github.com/imran-kn/cilium-fork@v1.6.9/pkg/envoy/xds/ack_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 xds 18 19 import ( 20 "context" 21 "time" 22 23 "github.com/cilium/cilium/pkg/checker" 24 "github.com/cilium/cilium/pkg/completion" 25 26 . "gopkg.in/check.v1" 27 ) 28 29 type AckSuite struct{} 30 31 var _ = Suite(&AckSuite{}) 32 33 const ( 34 node0 = "10.0.0.0" 35 node1 = "10.0.0.1" 36 node2 = "10.0.0.2" 37 ) 38 39 type compCheck struct { 40 err error 41 ch chan error 42 } 43 44 func newCompCheck() *compCheck { 45 return &compCheck{ 46 ch: make(chan error, 1), 47 } 48 } 49 50 func (c *compCheck) Err() error { 51 return c.err 52 } 53 54 // Return a new completion callback that will write the completion error to a channel 55 func newCompCallback() (func(error), *compCheck) { 56 comp := newCompCheck() 57 callback := func(err error) { 58 log.WithError(err).Debug("callback called") 59 comp.ch <- err 60 close(comp.ch) 61 } 62 return callback, comp 63 } 64 65 // IsCompletedChecker checks that a Completion is completed without errors. 66 type IsCompletedChecker struct { 67 *CheckerInfo 68 } 69 70 func (c *IsCompletedChecker) Check(params []interface{}, names []string) (result bool, err string) { 71 comp, ok := params[0].(*compCheck) 72 if !ok { 73 return false, "completion must be a *compCheck" 74 } 75 if comp == nil { 76 return false, "completion is nil" 77 } 78 79 // receive from a closed channel returns nil, so test for a previous error before trying again 80 if comp.err != nil { 81 return false, string(err) 82 } 83 84 select { 85 case comp.err = <-comp.ch: 86 return comp.err == nil, string(err) 87 default: 88 return false, "not completed yet" 89 } 90 } 91 92 // IsCompleted checks that a Completion is completed. 93 var IsCompleted Checker = &IsCompletedChecker{ 94 &CheckerInfo{Name: "IsCompleted", Params: []string{ 95 "completion"}}, 96 } 97 98 func (s *AckSuite) TestUpsertSingleNode(c *C) { 99 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 100 defer cancel() 101 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 102 wg := completion.NewWaitGroup(ctx) 103 104 // Empty cache is the version 1 105 cache := NewCache() 106 acker := NewAckingResourceMutatorWrapper(cache) 107 c.Assert(acker.ackedVersions, HasLen, 0) 108 109 // Create version 2 with resource 0. 110 callback, comp := newCompCallback() 111 acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback) 112 c.Assert(comp, Not(IsCompleted)) 113 c.Assert(acker.ackedVersions, HasLen, 0) 114 115 // Ack the right version, for the right resource, from another node. 116 acker.HandleResourceVersionAck(2, 2, node1, []string{resources[0].Name}, typeURL, "") 117 c.Assert(comp, Not(IsCompleted)) 118 c.Assert(acker.ackedVersions, HasLen, 1) 119 c.Assert(acker.ackedVersions[node1], Equals, uint64(2)) 120 121 // Ack the right version, for another resource, from the right node. 122 acker.HandleResourceVersionAck(2, 2, node0, []string{resources[1].Name}, typeURL, "") 123 c.Assert(comp, Not(IsCompleted)) 124 c.Assert(acker.ackedVersions, HasLen, 2) 125 c.Assert(acker.ackedVersions[node0], Equals, uint64(2)) 126 127 // Ack an older version, for the right resource, from the right node. 128 acker.HandleResourceVersionAck(1, 1, node0, []string{resources[0].Name}, typeURL, "") 129 c.Assert(comp, Not(IsCompleted)) 130 c.Assert(acker.ackedVersions, HasLen, 2) 131 c.Assert(acker.ackedVersions[node0], Equals, uint64(2)) 132 133 // Ack the right version, for the right resource, from the right node. 134 acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "") 135 c.Assert(comp, IsCompleted) 136 c.Assert(acker.ackedVersions, HasLen, 2) 137 c.Assert(acker.ackedVersions[node0], Equals, uint64(2)) 138 } 139 140 func (s *AckSuite) TestUseCurrent(c *C) { 141 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 142 defer cancel() 143 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 144 wg := completion.NewWaitGroup(ctx) 145 146 // Empty cache is the version 1 147 cache := NewCache() 148 acker := NewAckingResourceMutatorWrapper(cache) 149 c.Assert(acker.ackedVersions, HasLen, 0) 150 151 // Create version 2 with resource 0. 152 callback, comp := newCompCallback() 153 acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback) 154 c.Assert(comp, Not(IsCompleted)) 155 c.Assert(acker.ackedVersions, HasLen, 0) 156 c.Assert(acker.pendingCompletions, HasLen, 1) 157 158 // Ack the right version, for the right resource, from another node. 159 acker.HandleResourceVersionAck(2, 2, node1, []string{resources[0].Name}, typeURL, "") 160 c.Assert(comp, Not(IsCompleted)) 161 c.Assert(acker.ackedVersions, HasLen, 1) 162 c.Assert(acker.ackedVersions[node1], Equals, uint64(2)) 163 c.Assert(acker.pendingCompletions, HasLen, 1) 164 165 // Use current version, not yet acked 166 acker.UseCurrent(typeURL, []string{node0}, wg) 167 c.Assert(acker.pendingCompletions, HasLen, 2) 168 169 // Ack the right version, for another resource, from the right node. 170 acker.HandleResourceVersionAck(2, 2, node0, []string{resources[1].Name}, typeURL, "") 171 c.Assert(comp, Not(IsCompleted)) 172 c.Assert(acker.ackedVersions, HasLen, 2) 173 c.Assert(acker.ackedVersions[node0], Equals, uint64(2)) 174 // UseCurrent ignores resource names, so an ack of the same or later version from the right node will complete it 175 c.Assert(acker.pendingCompletions, HasLen, 1) 176 177 // Ack an older version, for the right resource, from the right node. 178 acker.HandleResourceVersionAck(1, 1, node0, []string{resources[0].Name}, typeURL, "") 179 c.Assert(comp, Not(IsCompleted)) 180 c.Assert(acker.ackedVersions, HasLen, 2) 181 c.Assert(acker.ackedVersions[node0], Equals, uint64(2)) 182 c.Assert(acker.pendingCompletions, HasLen, 1) 183 184 // Ack the right version, for the right resource, from the right node. 185 acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "") 186 c.Assert(comp, IsCompleted) 187 c.Assert(acker.ackedVersions, HasLen, 2) 188 c.Assert(acker.ackedVersions[node0], Equals, uint64(2)) 189 c.Assert(acker.pendingCompletions, HasLen, 0) 190 } 191 192 func (s *AckSuite) TestUpsertMultipleNodes(c *C) { 193 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 194 defer cancel() 195 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 196 wg := completion.NewWaitGroup(ctx) 197 198 // Empty cache is the version 1 199 cache := NewCache() 200 acker := NewAckingResourceMutatorWrapper(cache) 201 c.Assert(acker.ackedVersions, HasLen, 0) 202 203 // Create version 2 with resource 0. 204 callback, comp := newCompCallback() 205 acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0, node1}, wg, callback) 206 c.Assert(comp, Not(IsCompleted)) 207 c.Assert(acker.currentVersionAcked([]string{node0}), Equals, false) 208 c.Assert(acker.currentVersionAcked([]string{node1}), Equals, false) 209 c.Assert(acker.currentVersionAcked([]string{node2}), Equals, false) 210 211 // Ack the right version, for the right resource, from another node. 212 acker.HandleResourceVersionAck(2, 2, node2, []string{resources[0].Name}, typeURL, "") 213 c.Assert(comp, Not(IsCompleted)) 214 c.Assert(acker.currentVersionAcked([]string{node0}), Equals, false) 215 c.Assert(acker.currentVersionAcked([]string{node1}), Equals, false) 216 c.Assert(acker.currentVersionAcked([]string{node2}), Equals, true) 217 218 // Ack the right version, for the right resource, from one of the nodes (node0). 219 // One of the nodes (node1) still needs to ACK. 220 acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "") 221 c.Assert(comp, Not(IsCompleted)) 222 c.Assert(acker.currentVersionAcked([]string{node0}), Equals, true) 223 c.Assert(acker.currentVersionAcked([]string{node1}), Equals, false) 224 c.Assert(acker.currentVersionAcked([]string{node2}), Equals, true) 225 c.Assert(acker.currentVersionAcked([]string{node0, node1}), Equals, false) 226 c.Assert(acker.currentVersionAcked([]string{node0, node2}), Equals, true) 227 228 // Ack the right version, for the right resource, from the last remaining node (node1). 229 acker.HandleResourceVersionAck(2, 2, node1, []string{resources[0].Name}, typeURL, "") 230 c.Assert(comp, IsCompleted) 231 c.Assert(acker.currentVersionAcked([]string{node0}), Equals, true) 232 c.Assert(acker.currentVersionAcked([]string{node1}), Equals, true) 233 c.Assert(acker.currentVersionAcked([]string{node2}), Equals, true) 234 c.Assert(acker.currentVersionAcked([]string{node0, node1, node2}), Equals, true) 235 } 236 237 func (s *AckSuite) TestUpsertMoreRecentVersion(c *C) { 238 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 239 defer cancel() 240 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 241 wg := completion.NewWaitGroup(ctx) 242 243 // Empty cache is the version 1 244 cache := NewCache() 245 acker := NewAckingResourceMutatorWrapper(cache) 246 247 // Create version 2 with resource 0. 248 callback, comp := newCompCallback() 249 acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback) 250 c.Assert(comp, Not(IsCompleted)) 251 252 // Ack an older version, for the right resource, from the right node. 253 acker.HandleResourceVersionAck(1, 1, node0, []string{resources[0].Name}, typeURL, "") 254 c.Assert(comp, Not(IsCompleted)) 255 256 // Ack a more recent version, for the right resource, from the right node. 257 acker.HandleResourceVersionAck(123, 123, node0, []string{resources[0].Name}, typeURL, "") 258 c.Assert(comp, IsCompleted) 259 } 260 261 func (s *AckSuite) TestUpsertMoreRecentVersionNack(c *C) { 262 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 263 defer cancel() 264 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 265 wg := completion.NewWaitGroup(ctx) 266 267 // Empty cache is the version 1 268 cache := NewCache() 269 acker := NewAckingResourceMutatorWrapper(cache) 270 271 // Create version 2 with resource 0. 272 callback, comp := newCompCallback() 273 acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback) 274 c.Assert(comp, Not(IsCompleted)) 275 276 // Ack an older version, for the right resource, from the right node. 277 acker.HandleResourceVersionAck(1, 1, node0, []string{resources[0].Name}, typeURL, "") 278 c.Assert(comp, Not(IsCompleted)) 279 280 // NAck a more recent version, for the right resource, from the right node. 281 acker.HandleResourceVersionAck(1, 2, node0, []string{resources[0].Name}, typeURL, "Detail") 282 // IsCompleted is true only for completions without error 283 c.Assert(comp, Not(IsCompleted)) 284 c.Assert(comp.Err(), Not(Equals), nil) 285 c.Assert(comp.Err(), checker.DeepEquals, &ProxyError{Err: ErrNackReceived, Detail: "Detail"}) 286 } 287 288 func (s *AckSuite) TestDeleteSingleNode(c *C) { 289 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 290 defer cancel() 291 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 292 wg := completion.NewWaitGroup(ctx) 293 294 // Empty cache is the version 1 295 cache := NewCache() 296 acker := NewAckingResourceMutatorWrapper(cache) 297 298 // Create version 2 with resource 0. 299 callback, comp := newCompCallback() 300 acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback) 301 c.Assert(comp, Not(IsCompleted)) 302 303 // Ack the right version, for the right resource, from the right node. 304 acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "") 305 c.Assert(comp, IsCompleted) 306 307 // Create version 3 with no resources. 308 callback, comp = newCompCallback() 309 acker.Delete(typeURL, resources[0].Name, []string{node0}, wg, callback) 310 c.Assert(comp, Not(IsCompleted)) 311 312 // Ack the right version, for another resource, from another node. 313 acker.HandleResourceVersionAck(3, 3, node1, []string{resources[2].Name}, typeURL, "") 314 c.Assert(comp, Not(IsCompleted)) 315 316 // Ack the right version, for another resource, from the right node. 317 acker.HandleResourceVersionAck(3, 3, node0, []string{resources[2].Name}, typeURL, "") 318 // The resource name is ignored. For delete, we only consider the version. 319 c.Assert(comp, IsCompleted) 320 } 321 322 func (s *AckSuite) TestDeleteMultipleNodes(c *C) { 323 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 324 defer cancel() 325 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 326 wg := completion.NewWaitGroup(ctx) 327 328 // Empty cache is the version 1 329 cache := NewCache() 330 acker := NewAckingResourceMutatorWrapper(cache) 331 332 // Create version 2 with resource 0. 333 callback, comp := newCompCallback() 334 acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback) 335 c.Assert(comp, Not(IsCompleted)) 336 337 // Ack the right version, for the right resource, from the right node. 338 acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "") 339 c.Assert(comp, IsCompleted) 340 341 // Create version 3 with no resources. 342 callback, comp = newCompCallback() 343 acker.Delete(typeURL, resources[0].Name, []string{node0, node1}, wg, callback) 344 c.Assert(comp, Not(IsCompleted)) 345 346 // Ack the right version, for another resource, from one of the nodes. 347 acker.HandleResourceVersionAck(3, 3, node1, []string{resources[2].Name}, typeURL, "") 348 c.Assert(comp, Not(IsCompleted)) 349 350 // Ack the right version, for another resource, from the remaining node. 351 acker.HandleResourceVersionAck(3, 3, node0, []string{resources[2].Name}, typeURL, "") 352 // The resource name is ignored. For delete, we only consider the version. 353 c.Assert(comp, IsCompleted) 354 } 355 356 func (s *AckSuite) TestRevertInsert(c *C) { 357 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 358 defer cancel() 359 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 360 wg := completion.NewWaitGroup(ctx) 361 362 cache := NewCache() 363 acker := NewAckingResourceMutatorWrapper(cache) 364 365 // Create version 1 with resource 0. 366 // Insert. 367 revert := acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, nil, nil) 368 369 // Insert another resource. 370 _ = acker.Upsert(typeURL, resources[2].Name, resources[2], []string{node0}, nil, nil) 371 372 res, err := cache.Lookup(typeURL, resources[0].Name) 373 c.Assert(err, IsNil) 374 c.Assert(res, Equals, resources[0]) 375 376 res, err = cache.Lookup(typeURL, resources[2].Name) 377 c.Assert(err, IsNil) 378 c.Assert(res, Equals, resources[2]) 379 380 comp := wg.AddCompletion() 381 defer comp.Complete(nil) 382 revert(comp) 383 384 res, err = cache.Lookup(typeURL, resources[0].Name) 385 c.Assert(err, IsNil) 386 c.Assert(res, IsNil) 387 388 res, err = cache.Lookup(typeURL, resources[2].Name) 389 c.Assert(err, IsNil) 390 c.Assert(res, Equals, resources[2]) 391 } 392 393 func (s *AckSuite) TestRevertUpdate(c *C) { 394 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 395 defer cancel() 396 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 397 wg := completion.NewWaitGroup(ctx) 398 399 cache := NewCache() 400 acker := NewAckingResourceMutatorWrapper(cache) 401 402 // Create version 1 with resource 0. 403 // Insert. 404 acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, nil, nil) 405 406 // Insert another resource. 407 _ = acker.Upsert(typeURL, resources[2].Name, resources[2], []string{node0}, nil, nil) 408 409 res, err := cache.Lookup(typeURL, resources[0].Name) 410 c.Assert(err, IsNil) 411 c.Assert(res, Equals, resources[0]) 412 413 res, err = cache.Lookup(typeURL, resources[2].Name) 414 c.Assert(err, IsNil) 415 c.Assert(res, Equals, resources[2]) 416 417 // Update. 418 revert := acker.Upsert(typeURL, resources[0].Name, resources[1], []string{node0}, nil, nil) 419 420 res, err = cache.Lookup(typeURL, resources[0].Name) 421 c.Assert(err, IsNil) 422 c.Assert(res, Equals, resources[1]) 423 424 comp := wg.AddCompletion() 425 defer comp.Complete(nil) 426 revert(comp) 427 428 res, err = cache.Lookup(typeURL, resources[0].Name) 429 c.Assert(err, IsNil) 430 c.Assert(res, Equals, resources[0]) 431 432 res, err = cache.Lookup(typeURL, resources[2].Name) 433 c.Assert(err, IsNil) 434 c.Assert(res, Equals, resources[2]) 435 } 436 437 func (s *AckSuite) TestRevertDelete(c *C) { 438 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 439 defer cancel() 440 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 441 wg := completion.NewWaitGroup(ctx) 442 443 cache := NewCache() 444 acker := NewAckingResourceMutatorWrapper(cache) 445 446 // Create version 1 with resource 0. 447 // Insert. 448 acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, nil, nil) 449 450 // Insert another resource. 451 _ = acker.Upsert(typeURL, resources[2].Name, resources[2], []string{node0}, nil, nil) 452 453 res, err := cache.Lookup(typeURL, resources[0].Name) 454 c.Assert(err, IsNil) 455 c.Assert(res, Equals, resources[0]) 456 457 res, err = cache.Lookup(typeURL, resources[2].Name) 458 c.Assert(err, IsNil) 459 c.Assert(res, Equals, resources[2]) 460 461 // Delete. 462 revert := acker.Delete(typeURL, resources[0].Name, []string{node0}, nil, nil) 463 464 res, err = cache.Lookup(typeURL, resources[0].Name) 465 c.Assert(err, IsNil) 466 c.Assert(res, IsNil) 467 468 res, err = cache.Lookup(typeURL, resources[2].Name) 469 c.Assert(err, IsNil) 470 c.Assert(res, Equals, resources[2]) 471 472 comp := wg.AddCompletion() 473 defer comp.Complete(nil) 474 revert(comp) 475 476 res, err = cache.Lookup(typeURL, resources[0].Name) 477 c.Assert(err, IsNil) 478 c.Assert(res, Equals, resources[0]) 479 480 res, err = cache.Lookup(typeURL, resources[2].Name) 481 c.Assert(err, IsNil) 482 c.Assert(res, Equals, resources[2]) 483 }