github.com/looshlee/cilium@v1.6.12/daemon/endpoint_test.go (about) 1 // Copyright 2018 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // +build !privileged_tests 16 17 package main 18 19 import ( 20 "context" 21 "net" 22 "runtime" 23 "time" 24 25 "github.com/cilium/cilium/api/v1/models" 26 apiEndpoint "github.com/cilium/cilium/api/v1/server/restapi/endpoint" 27 "github.com/cilium/cilium/pkg/checker" 28 "github.com/cilium/cilium/pkg/endpoint" 29 endpointid "github.com/cilium/cilium/pkg/endpoint/id" 30 "github.com/cilium/cilium/pkg/endpointmanager" 31 "github.com/cilium/cilium/pkg/eventqueue" 32 "github.com/cilium/cilium/pkg/identity" 33 "github.com/cilium/cilium/pkg/labels" 34 "github.com/cilium/cilium/pkg/metrics" 35 "github.com/cilium/cilium/pkg/option" 36 "github.com/cilium/cilium/pkg/testutils" 37 38 . "gopkg.in/check.v1" 39 ) 40 41 func getEPTemplate(c *C, d *Daemon) *models.EndpointChangeRequest { 42 ip4, ip6, err := d.ipam.AllocateNext("", "test") 43 c.Assert(err, Equals, nil) 44 c.Assert(ip4, Not(IsNil)) 45 c.Assert(ip6, Not(IsNil)) 46 47 return &models.EndpointChangeRequest{ 48 ContainerName: "foo", 49 State: models.EndpointStateWaitingForIdentity, 50 Addressing: &models.AddressPair{ 51 IPV6: ip6.IP.String(), 52 IPV4: ip4.IP.String(), 53 }, 54 } 55 } 56 57 func (ds *DaemonSuite) TestEndpointAddReservedLabel(c *C) { 58 assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0) 59 60 epTemplate := getEPTemplate(c, ds.d) 61 epTemplate.Labels = []string{"reserved:world"} 62 _, code, err := ds.d.createEndpoint(context.TODO(), epTemplate) 63 c.Assert(err, Not(IsNil)) 64 c.Assert(code, Equals, apiEndpoint.PutEndpointIDInvalidCode) 65 66 // Endpoint was created with invalid data; should transition from 67 // WaitForIdentity -> Invalid. 68 assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0) 69 assertOnMetric(c, string(models.EndpointStateInvalid), 0) 70 71 // Endpoint is created with inital label as well as disallowed 72 // reserved:world label. 73 epTemplate.Labels = append(epTemplate.Labels, "reserved:init") 74 _, code, err = ds.d.createEndpoint(context.TODO(), epTemplate) 75 c.Assert(err, ErrorMatches, "not allowed to add reserved labels:.+") 76 c.Assert(code, Equals, apiEndpoint.PutEndpointIDInvalidCode) 77 78 // Endpoint was created with invalid data; should transition from 79 // WaitForIdentity -> Invalid. 80 assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0) 81 assertOnMetric(c, string(models.EndpointStateInvalid), 0) 82 } 83 84 func (ds *DaemonSuite) TestEndpointAddInvalidLabel(c *C) { 85 assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0) 86 87 epTemplate := getEPTemplate(c, ds.d) 88 epTemplate.Labels = []string{"reserved:foo"} 89 _, code, err := ds.d.createEndpoint(context.TODO(), epTemplate) 90 c.Assert(err, Not(IsNil)) 91 c.Assert(code, Equals, apiEndpoint.PutEndpointIDInvalidCode) 92 93 // Endpoint was created with invalid data; should transition from 94 // WaitForIdentity -> Invalid. 95 assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0) 96 assertOnMetric(c, string(models.EndpointStateInvalid), 0) 97 } 98 99 func (ds *DaemonSuite) TestEndpointAddNoLabels(c *C) { 100 assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0) 101 102 // Create the endpoint without any labels. 103 epTemplate := getEPTemplate(c, ds.d) 104 _, _, err := ds.d.createEndpoint(context.TODO(), epTemplate) 105 c.Assert(err, IsNil) 106 107 // Endpoint enters WaitingToRegenerate as it has its labels updated during 108 // creation. 109 assertOnMetric(c, string(models.EndpointStateWaitingToRegenerate), 1) 110 111 expectedLabels := labels.Labels{ 112 labels.IDNameInit: labels.NewLabel(labels.IDNameInit, "", labels.LabelSourceReserved), 113 } 114 // Check that the endpoint has the reserved:init label. 115 ep, err := endpointmanager.Lookup(endpointid.NewIPPrefixID(net.ParseIP(epTemplate.Addressing.IPV4))) 116 c.Assert(err, IsNil) 117 c.Assert(ep.OpLabels.IdentityLabels(), checker.DeepEquals, expectedLabels) 118 119 // Check that the endpoint received the reserved identity for the 120 // reserved:init entities. 121 timeout := time.NewTimer(3 * time.Second) 122 defer timeout.Stop() 123 tick := time.NewTicker(200 * time.Millisecond) 124 defer tick.Stop() 125 var secID *identity.Identity 126 Loop: 127 for { 128 select { 129 case <-timeout.C: 130 break Loop 131 case <-tick.C: 132 ep.UnconditionalRLock() 133 secID = ep.SecurityIdentity 134 ep.RUnlock() 135 if secID != nil { 136 break Loop 137 } 138 } 139 } 140 c.Assert(secID, Not(IsNil)) 141 c.Assert(secID.ID, Equals, identity.ReservedIdentityInit) 142 143 // Endpoint should transition from WaitingToRegenerate -> Ready. 144 assertOnMetric(c, string(models.EndpointStateWaitingToRegenerate), 0) 145 assertOnMetric(c, string(models.EndpointStateReady), 1) 146 } 147 148 func (ds *DaemonSuite) TestUpdateSecLabels(c *C) { 149 lbls := labels.NewLabelsFromModel([]string{"reserved:world"}) 150 code, err := ds.d.modifyEndpointIdentityLabelsFromAPI("1", lbls, nil) 151 c.Assert(err, Not(IsNil)) 152 c.Assert(code, Equals, apiEndpoint.PatchEndpointIDLabelsUpdateFailedCode) 153 } 154 155 func (ds *DaemonSuite) TestUpdateLabelsFailed(c *C) { 156 cancelledContext, cancelFunc := context.WithTimeout(context.Background(), 1*time.Second) 157 cancelFunc() // Cancel immediatly to trigger the codepath to test. 158 159 // Create the endpoint without any labels. 160 epTemplate := getEPTemplate(c, ds.d) 161 _, _, err := ds.d.createEndpoint(cancelledContext, epTemplate) 162 c.Assert(err, ErrorMatches, "request cancelled while resolving identity") 163 164 assertOnMetric(c, string(models.EndpointStateReady), 0) 165 } 166 167 func getMetricValue(state string) int64 { 168 return int64(metrics.GetGaugeValue(metrics.EndpointStateCount.WithLabelValues(state))) 169 } 170 171 func assertOnMetric(c *C, state string, expected int64) { 172 _, _, line, _ := runtime.Caller(1) 173 174 obtainedValues := make(map[int64]struct{}, 0) 175 err := testutils.WaitUntil(func() bool { 176 obtained := getMetricValue(state) 177 obtainedValues[obtained] = struct{}{} 178 return obtained == expected 179 }, 10*time.Second) 180 if err != nil { 181 // We are printing the map here to show every unique obtained metrics 182 // value because these values change rapidly and it may be misleading 183 // to only show the last obtained value. 184 c.Errorf("Metrics assertion failed on line %d for Endpoint state %s: obtained %v, expected %d", 185 line, state, obtainedValues, expected) 186 } 187 } 188 189 type EndpointDeadlockEvent struct { 190 ep *endpoint.Endpoint 191 deadlockChan chan struct{} 192 } 193 194 var ( 195 deadlockTimeout = 2 * time.Second 196 deadlockTestTimeout = 3*deadlockTimeout + 1*time.Second 197 ) 198 199 func (n *EndpointDeadlockEvent) Handle(ifc chan interface{}) { 200 // We need to sleep here so that we are consuming an event off the queue, 201 // but not acquiring the lock yet. 202 // There isn't much of a better way to ensure that an Event is being 203 // processed off of the EventQueue, but hasn't acquired the Endpoint's 204 // lock *before* we call deleteEndpointQuiet (see below test). 205 close(n.deadlockChan) 206 time.Sleep(deadlockTimeout) 207 n.ep.UnconditionalLock() 208 n.ep.Unlock() 209 } 210 211 // This unit test is a bit weird - see 212 // https://github.com/cilium/cilium/pull/8687 . 213 func (ds *DaemonSuite) TestEndpointEventQueueDeadlockUponDeletion(c *C) { 214 // Need to modify global configuration (hooray!), change back when test is 215 // done. 216 oldQueueSize := option.Config.EndpointQueueSize 217 option.Config.EndpointQueueSize = 1 218 defer func() { 219 option.Config.EndpointQueueSize = oldQueueSize 220 }() 221 222 // Create the endpoint without any labels. 223 epTemplate := getEPTemplate(c, ds.d) 224 ep, _, err := ds.d.createEndpoint(context.TODO(), epTemplate) 225 c.Assert(err, IsNil) 226 c.Assert(ep, Not(IsNil)) 227 228 // In case deadlock occurs, provide a timeout of 3 (number of events) * 229 // deadlockTimeout + 1 seconds to ensure that we are actually testing for 230 // deadlock, and not prematurely exiting, and also so the test suite doesn't 231 // hang forever. 232 ctx, cancel := context.WithTimeout(context.Background(), deadlockTestTimeout) 233 defer cancel() 234 235 // Create three events that go on the endpoint's EventQueue. We need three 236 // events because the first event enqueued immediately is consumed off of 237 // the queue; the second event is put onto the queue (which has length of 238 // one), and the third queue is waiting for the queue's buffer to not be 239 // full (e.g., the first event is finished processing). If the first event 240 // gets stuck processing forever due to deadlock, then the third event 241 // will never be consumed, and the endpoint's EventQueue will never be 242 // closed because Enqueue gets stuck. 243 ev1Ch := make(chan struct{}) 244 ev2Ch := make(chan struct{}) 245 ev3Ch := make(chan struct{}) 246 247 ev := eventqueue.NewEvent(&EndpointDeadlockEvent{ 248 ep: ep, 249 deadlockChan: ev1Ch, 250 }) 251 252 ev2 := eventqueue.NewEvent(&EndpointDeadlockEvent{ 253 ep: ep, 254 deadlockChan: ev2Ch, 255 }) 256 257 ev3 := eventqueue.NewEvent(&EndpointDeadlockEvent{ 258 ep: ep, 259 deadlockChan: ev3Ch, 260 }) 261 262 ev2EnqueueCh := make(chan struct{}) 263 264 go func() { 265 _, err := ep.EventQueue.Enqueue(ev) 266 c.Assert(err, IsNil) 267 _, err = ep.EventQueue.Enqueue(ev2) 268 c.Assert(err, IsNil) 269 close(ev2EnqueueCh) 270 _, err = ep.EventQueue.Enqueue(ev3) 271 c.Assert(err, IsNil) 272 }() 273 274 // Ensure that the second event is enqueued before proceeding further, as 275 // we need to assume that at least one event is being processed, and another 276 // one is pushed onto the endpoint's EventQueue. 277 <-ev2EnqueueCh 278 epDelComplete := make(chan struct{}) 279 280 // Launch endpoint deletion async so that we do not deadlock (which is what 281 // this unit test is designed to test). 282 go func(ch chan struct{}) { 283 errors := ds.d.deleteEndpointQuiet(ep, endpoint.DeleteConfig{}) 284 c.Assert(errors, Not(IsNil)) 285 epDelComplete <- struct{}{} 286 }(epDelComplete) 287 288 select { 289 case <-ctx.Done(): 290 c.Log("endpoint deletion did not complete in time") 291 c.Fail() 292 case <-epDelComplete: 293 // Success, do nothing. 294 } 295 }