github.com/imran-kn/cilium-fork@v1.6.9/pkg/envoy/xds/server_e2e_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 "errors" 22 "fmt" 23 "reflect" 24 "sort" 25 "testing" 26 "time" 27 28 "github.com/cilium/cilium/pkg/checker" 29 "github.com/cilium/cilium/pkg/completion" 30 envoy_api_v2 "github.com/cilium/proxy/go/envoy/api/v2" 31 envoy_api_v2_core "github.com/cilium/proxy/go/envoy/api/v2/core" 32 33 "github.com/golang/protobuf/proto" 34 "github.com/golang/protobuf/ptypes/any" 35 "google.golang.org/genproto/googleapis/rpc/status" 36 . "gopkg.in/check.v1" 37 ) 38 39 // Hook up gocheck into the "go test" runner. 40 func Test(t *testing.T) { 41 // logging.ToggleDebugLogs(true) 42 TestingT(t) 43 } 44 45 type ServerSuite struct{} 46 47 var _ = Suite(&ServerSuite{}) 48 49 const ( 50 TestTimeout = 10 * time.Second 51 StreamTimeout = 2 * time.Second 52 CacheUpdateDelay = 250 * time.Millisecond 53 ) 54 55 var ( 56 DeferredCompletion error = errors.New("Deferred completion") 57 nodes = map[string]*envoy_api_v2_core.Node{ 58 node0: {Id: "sidecar~10.0.0.0~node0~bar"}, 59 node1: {Id: "sidecar~10.0.0.1~node1~bar"}, 60 node2: {Id: "sidecar~10.0.0.2~node2~bar"}, 61 } 62 ) 63 64 // ResponseMatchesChecker checks that a DiscoveryResponse's fields match the given 65 // parameters. 66 type ResponseMatchesChecker struct { 67 *CheckerInfo 68 } 69 70 func (c *ResponseMatchesChecker) Check(params []interface{}, names []string) (result bool, error string) { 71 response, ok := params[0].(*envoy_api_v2.DiscoveryResponse) 72 if !ok { 73 return false, "response must be an *envoy_api_v2.DiscoveryResponse" 74 } 75 if response == nil { 76 return false, "response is nil" 77 } 78 79 versionInfo, ok := params[1].(string) 80 if !ok { 81 return false, "VersionInfo must be a string" 82 } 83 resources, ok := params[2].([]proto.Message) 84 if params[2] != nil && !ok { 85 return false, "Resources must be a []proto.Message" 86 } 87 canary, ok := params[3].(bool) 88 if !ok { 89 return false, "Canary must be a bool" 90 } 91 typeURL, ok := params[4].(string) 92 if !ok { 93 return false, "TypeURL must be a string" 94 } 95 96 error = "" 97 98 result = response.VersionInfo == versionInfo && 99 len(response.Resources) == len(resources) && 100 response.Canary == canary && 101 response.TypeUrl == typeURL 102 103 if result && len(resources) > 0 { 104 // Convert the resources into Any protocol buffer messages, which is 105 // the type of Resources in the response, so that we can compare them. 106 resourcesAny := make([]*any.Any, 0, len(resources)) 107 for _, res := range resources { 108 data, err := proto.Marshal(res) 109 if err != nil { 110 return false, fmt.Sprintf("error marshalling protocol buffer %v", res) 111 } 112 resourcesAny = append(resourcesAny, 113 &any.Any{ 114 TypeUrl: typeURL, 115 Value: data, 116 }) 117 } 118 // Sort both lists. 119 sort.Slice(response.Resources, func(i, j int) bool { 120 return response.Resources[i].String() < response.Resources[j].String() 121 }) 122 sort.Slice(resourcesAny, func(i, j int) bool { 123 return resourcesAny[i].String() < resourcesAny[j].String() 124 }) 125 result = reflect.DeepEqual(response.Resources, resourcesAny) 126 } 127 128 return 129 } 130 131 // ResponseMatches checks that a DiscoveryResponse's fields match the given 132 // parameters. 133 var ResponseMatches Checker = &ResponseMatchesChecker{ 134 &CheckerInfo{Name: "ResponseMatches", Params: []string{ 135 "response", "VersionInfo", "Resources", "Canary", "TypeUrl"}}, 136 } 137 138 var resources = []*envoy_api_v2.RouteConfiguration{ 139 {Name: "resource0"}, 140 {Name: "resource1"}, 141 {Name: "resource2"}, 142 } 143 144 func (s *ServerSuite) TestRequestAllResources(c *C) { 145 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 146 147 var err error 148 var req *envoy_api_v2.DiscoveryRequest 149 var resp *envoy_api_v2.DiscoveryResponse 150 var v uint64 151 var mod bool 152 153 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 154 defer cancel() 155 156 cache := NewCache() 157 mutator := NewAckingResourceMutatorWrapper(cache) 158 159 streamCtx, closeStream := context.WithCancel(ctx) 160 stream := NewMockStream(streamCtx, 1, 1, StreamTimeout, StreamTimeout) 161 defer stream.Close() 162 163 server := NewServer(map[string]*ResourceTypeConfiguration{typeURL: {Source: cache, AckObserver: mutator}}, 164 TestTimeout) 165 166 streamDone := make(chan struct{}) 167 168 // Run the server's stream handler concurrently. 169 go func() { 170 err := server.HandleRequestStream(ctx, stream, AnyTypeURL) 171 close(streamDone) 172 c.Check(err, IsNil) 173 }() 174 175 // Request all resources. 176 req = &envoy_api_v2.DiscoveryRequest{ 177 TypeUrl: typeURL, 178 VersionInfo: "", 179 Node: nodes[node0], 180 ResourceNames: nil, 181 ResponseNonce: "", 182 } 183 err = stream.SendRequest(req) 184 c.Assert(err, IsNil) 185 186 // Expecting an empty response. 187 resp, err = stream.RecvResponse() 188 c.Assert(err, IsNil) 189 c.Assert(resp, ResponseMatches, "1", nil, false, typeURL) 190 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 191 192 // Request the next version of resources. 193 req = &envoy_api_v2.DiscoveryRequest{ 194 TypeUrl: typeURL, 195 VersionInfo: resp.VersionInfo, // ACK the received version. 196 Node: nodes[node0], 197 ResourceNames: nil, 198 ResponseNonce: resp.Nonce, 199 } 200 err = stream.SendRequest(req) 201 c.Assert(err, IsNil) 202 203 // Create version 2 with resource 0. 204 time.Sleep(CacheUpdateDelay) 205 v, mod, _ = cache.Upsert(typeURL, resources[0].Name, resources[0]) 206 c.Assert(v, Equals, uint64(2)) 207 c.Assert(mod, Equals, true) 208 209 // Expecting a response with that resource. 210 resp, err = stream.RecvResponse() 211 c.Assert(err, IsNil) 212 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 213 c.Assert(resp, ResponseMatches, "2", []proto.Message{resources[0]}, false, typeURL) 214 215 // Create version 3 with resources 0 and 1. 216 // This time, update the cache before sending the request. 217 v, mod, _ = cache.Upsert(typeURL, resources[1].Name, resources[1]) 218 c.Assert(v, Equals, uint64(3)) 219 c.Assert(mod, Equals, true) 220 221 // Request the next version of resources. 222 req = &envoy_api_v2.DiscoveryRequest{ 223 TypeUrl: typeURL, 224 VersionInfo: resp.VersionInfo, // ACK the received version. 225 Node: nodes[node0], 226 ResourceNames: nil, 227 ResponseNonce: resp.Nonce, 228 } 229 err = stream.SendRequest(req) 230 c.Assert(err, IsNil) 231 232 // Expecting a response with both resources. 233 resp, err = stream.RecvResponse() 234 c.Assert(err, IsNil) 235 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 236 c.Assert(resp, ResponseMatches, "3", []proto.Message{resources[0], resources[1]}, false, typeURL) 237 238 // Request the next version of resources. 239 req = &envoy_api_v2.DiscoveryRequest{ 240 TypeUrl: typeURL, 241 VersionInfo: resp.VersionInfo, // ACK the received version. 242 Node: nodes[node0], 243 ResourceNames: nil, 244 ResponseNonce: resp.Nonce, 245 } 246 err = stream.SendRequest(req) 247 c.Assert(err, IsNil) 248 249 // Create version 4 with resource 1. 250 time.Sleep(CacheUpdateDelay) 251 v, mod, _ = cache.Delete(typeURL, resources[0].Name) 252 c.Assert(v, Equals, uint64(4)) 253 c.Assert(mod, Equals, true) 254 255 // Expecting a response with that resource. 256 resp, err = stream.RecvResponse() 257 c.Assert(err, IsNil) 258 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 259 c.Assert(resp, ResponseMatches, "4", []proto.Message{resources[1]}, false, typeURL) 260 261 // Close the stream. 262 closeStream() 263 264 select { 265 case <-ctx.Done(): 266 c.Errorf("HandleRequestStream(%v, %v, %v) took too long to return after stream was closed", "ctx", "stream", AnyTypeURL) 267 case <-streamDone: 268 } 269 } 270 271 func (s *ServerSuite) TestAck(c *C) { 272 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 273 274 var err error 275 var req *envoy_api_v2.DiscoveryRequest 276 var resp *envoy_api_v2.DiscoveryResponse 277 278 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 279 defer cancel() 280 wg := completion.NewWaitGroup(ctx) 281 282 cache := NewCache() 283 mutator := NewAckingResourceMutatorWrapper(cache) 284 285 streamCtx, closeStream := context.WithCancel(ctx) 286 stream := NewMockStream(streamCtx, 1, 1, StreamTimeout, StreamTimeout) 287 defer stream.Close() 288 289 server := NewServer(map[string]*ResourceTypeConfiguration{typeURL: {Source: cache, AckObserver: mutator}}, 290 TestTimeout) 291 292 streamDone := make(chan struct{}) 293 294 // Run the server's stream handler concurrently. 295 go func() { 296 err := server.HandleRequestStream(ctx, stream, AnyTypeURL) 297 close(streamDone) 298 c.Check(err, IsNil) 299 }() 300 301 // Request all resources. 302 req = &envoy_api_v2.DiscoveryRequest{ 303 TypeUrl: typeURL, 304 VersionInfo: "", 305 Node: nodes[node0], 306 ResourceNames: nil, 307 ResponseNonce: "", 308 } 309 err = stream.SendRequest(req) 310 c.Assert(err, IsNil) 311 312 // Expecting an empty response. 313 resp, err = stream.RecvResponse() 314 c.Assert(err, IsNil) 315 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 316 c.Assert(resp, ResponseMatches, "1", nil, false, typeURL) 317 318 // Request the next version of resources. 319 req = &envoy_api_v2.DiscoveryRequest{ 320 TypeUrl: typeURL, 321 VersionInfo: resp.VersionInfo, // ACK the received version. 322 Node: nodes[node0], 323 ResourceNames: nil, 324 ResponseNonce: resp.Nonce, 325 } 326 err = stream.SendRequest(req) 327 c.Assert(err, IsNil) 328 329 // Create version 2 with resource 0. 330 time.Sleep(CacheUpdateDelay) 331 callback1, comp1 := newCompCallback() 332 mutator.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback1) 333 c.Assert(comp1, Not(IsCompleted)) 334 335 // Expecting a response with that resource. 336 resp, err = stream.RecvResponse() 337 c.Assert(err, IsNil) 338 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 339 c.Assert(resp, ResponseMatches, "2", []proto.Message{resources[0]}, false, typeURL) 340 341 // Create version 3 with resources 0 and 1. 342 // This time, update the cache before sending the request. 343 callback2, comp2 := newCompCallback() 344 mutator.Upsert(typeURL, resources[1].Name, resources[1], []string{node0}, wg, callback2) 345 c.Assert(comp2, Not(IsCompleted)) 346 347 // Request the next version of resources. 348 req = &envoy_api_v2.DiscoveryRequest{ 349 TypeUrl: typeURL, 350 VersionInfo: resp.VersionInfo, // ACK the received version. 351 Node: nodes[node0], 352 ResourceNames: nil, 353 ResponseNonce: resp.Nonce, 354 } 355 err = stream.SendRequest(req) 356 c.Assert(err, IsNil) 357 358 // Expecting a response with both resources. 359 resp, err = stream.RecvResponse() 360 c.Assert(err, IsNil) 361 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 362 c.Assert(resp, ResponseMatches, "3", []proto.Message{resources[0], resources[1]}, false, typeURL) 363 364 // Version 2 was ACKed by the last request. 365 c.Assert(comp1, IsCompleted) 366 c.Assert(comp2, Not(IsCompleted)) 367 368 // Request the next version of resources. 369 req = &envoy_api_v2.DiscoveryRequest{ 370 TypeUrl: typeURL, 371 VersionInfo: resp.VersionInfo, // ACK the received version. 372 Node: nodes[node0], 373 ResourceNames: nil, 374 ResponseNonce: resp.Nonce, 375 } 376 err = stream.SendRequest(req) 377 c.Assert(err, IsNil) 378 379 // Expecting no response. 380 381 time.Sleep(CacheUpdateDelay) 382 383 // Version 3 was ACKed by the last request. 384 c.Assert(comp2, IsCompleted) 385 386 // Close the stream. 387 closeStream() 388 389 select { 390 case <-ctx.Done(): 391 c.Errorf("HandleRequestStream(%v, %v, %v) took too long to return after stream was closed", "ctx", "stream", AnyTypeURL) 392 case <-streamDone: 393 } 394 } 395 396 func (s *ServerSuite) TestRequestSomeResources(c *C) { 397 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 398 399 var err error 400 var req *envoy_api_v2.DiscoveryRequest 401 var resp *envoy_api_v2.DiscoveryResponse 402 var v uint64 403 var mod bool 404 405 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 406 defer cancel() 407 408 cache := NewCache() 409 mutator := NewAckingResourceMutatorWrapper(cache) 410 411 streamCtx, closeStream := context.WithCancel(ctx) 412 stream := NewMockStream(streamCtx, 1, 1, StreamTimeout, StreamTimeout) 413 defer stream.Close() 414 415 server := NewServer(map[string]*ResourceTypeConfiguration{typeURL: {Source: cache, AckObserver: mutator}}, 416 TestTimeout) 417 418 streamDone := make(chan struct{}) 419 420 // Run the server's stream handler concurrently. 421 go func() { 422 err := server.HandleRequestStream(ctx, stream, AnyTypeURL) 423 close(streamDone) 424 c.Check(err, IsNil) 425 }() 426 427 // Request resources 1 and 2 (not 0). 428 req = &envoy_api_v2.DiscoveryRequest{ 429 TypeUrl: typeURL, 430 VersionInfo: "", 431 Node: nodes[node0], 432 ResourceNames: []string{resources[1].Name, resources[2].Name}, 433 ResponseNonce: "", 434 } 435 err = stream.SendRequest(req) 436 c.Assert(err, IsNil) 437 438 // Expecting an empty response. 439 resp, err = stream.RecvResponse() 440 c.Assert(err, IsNil) 441 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 442 c.Assert(resp, ResponseMatches, "1", nil, false, typeURL) 443 444 // Request the next version of resources. 445 req = &envoy_api_v2.DiscoveryRequest{ 446 TypeUrl: typeURL, 447 VersionInfo: resp.VersionInfo, // ACK the received version. 448 Node: nodes[node0], 449 ResourceNames: []string{resources[1].Name, resources[2].Name}, 450 ResponseNonce: resp.Nonce, 451 } 452 err = stream.SendRequest(req) 453 c.Assert(err, IsNil) 454 455 // Create version 2 with resource 0. 456 time.Sleep(CacheUpdateDelay) 457 v, mod, _ = cache.Upsert(typeURL, resources[0].Name, resources[0]) 458 c.Assert(v, Equals, uint64(2)) 459 c.Assert(mod, Equals, true) 460 461 // There should be a response with no resources. 462 resp, err = stream.RecvResponse() 463 c.Assert(err, IsNil) 464 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 465 c.Assert(resp, ResponseMatches, "2", nil, false, typeURL) 466 467 // Create version 3 with resource 0 and 1. 468 // This time, update the cache before sending the request. 469 v, mod, _ = cache.Upsert(typeURL, resources[1].Name, resources[1]) 470 c.Assert(v, Equals, uint64(3)) 471 c.Assert(mod, Equals, true) 472 473 // Request the next version of resources. 474 req = &envoy_api_v2.DiscoveryRequest{ 475 TypeUrl: typeURL, 476 VersionInfo: resp.VersionInfo, // ACK the received version. 477 Node: nodes[node0], 478 ResourceNames: []string{resources[1].Name, resources[2].Name}, 479 ResponseNonce: resp.Nonce, 480 } 481 err = stream.SendRequest(req) 482 c.Assert(err, IsNil) 483 484 // Expecting a response with one resource. 485 resp, err = stream.RecvResponse() 486 c.Assert(err, IsNil) 487 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 488 c.Assert(resp, ResponseMatches, "3", []proto.Message{resources[1]}, false, typeURL) 489 490 // Request the next version of resources. 491 req = &envoy_api_v2.DiscoveryRequest{ 492 TypeUrl: typeURL, 493 VersionInfo: resp.VersionInfo, // ACK the received version. 494 Node: nodes[node0], 495 ResourceNames: []string{resources[1].Name, resources[2].Name}, 496 ResponseNonce: resp.Nonce, 497 } 498 err = stream.SendRequest(req) 499 c.Assert(err, IsNil) 500 501 // Create version 4 with resources 0, 1 and 2. 502 time.Sleep(CacheUpdateDelay) 503 v, mod, _ = cache.Upsert(typeURL, resources[2].Name, resources[2]) 504 c.Assert(v, Equals, uint64(4)) 505 c.Assert(mod, Equals, true) 506 507 // Expecting a response with resources 1 and 2. 508 resp, err = stream.RecvResponse() 509 c.Assert(err, IsNil) 510 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 511 c.Assert(resp, ResponseMatches, "4", []proto.Message{resources[1], resources[2]}, false, typeURL) 512 513 // Request the next version of resources. 514 req = &envoy_api_v2.DiscoveryRequest{ 515 TypeUrl: typeURL, 516 VersionInfo: resp.VersionInfo, // ACK the received version. 517 Node: nodes[node0], 518 ResourceNames: []string{resources[1].Name, resources[2].Name}, 519 ResponseNonce: resp.Nonce, 520 } 521 err = stream.SendRequest(req) 522 c.Assert(err, IsNil) 523 524 // Create version 5 with resources 1 and 2. 525 time.Sleep(CacheUpdateDelay) 526 v, mod, _ = cache.Delete(typeURL, resources[0].Name) 527 c.Assert(v, Equals, uint64(5)) 528 c.Assert(mod, Equals, true) 529 530 // Expecting no response for version 5, since neither resources 1 and 2 531 // have changed. 532 533 // Updating resource 2 with the exact same value won't increase the version 534 // number. Remain at version 5. 535 v, mod, _ = cache.Upsert(typeURL, resources[2].Name, resources[2]) 536 c.Assert(v, Equals, uint64(5)) 537 c.Assert(mod, Equals, false) 538 539 // Create version 6 with resource 1. 540 v, mod, _ = cache.Delete(typeURL, resources[1].Name) 541 c.Assert(v, Equals, uint64(6)) 542 c.Assert(mod, Equals, true) 543 544 // Expecting a response with resource 2. 545 resp, err = stream.RecvResponse() 546 c.Assert(err, IsNil) 547 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 548 c.Assert(resp, ResponseMatches, "6", []proto.Message{resources[2]}, false, typeURL) 549 550 // Resource 1 has been deleted; Resource 2 exists. Confirm using Lookup(). 551 rsrc, err := cache.Lookup(typeURL, resources[1].Name) 552 c.Assert(err, IsNil) 553 c.Assert(rsrc, IsNil) 554 555 rsrc, err = cache.Lookup(typeURL, resources[2].Name) 556 c.Assert(err, IsNil) 557 c.Assert(rsrc, Not(IsNil)) 558 c.Assert(rsrc.(*envoy_api_v2.RouteConfiguration), checker.DeepEquals, resources[2]) 559 560 // Close the stream. 561 closeStream() 562 563 select { 564 case <-ctx.Done(): 565 c.Errorf("HandleRequestStream(%v, %v, %v) took too long to return after stream was closed", "ctx", "stream", AnyTypeURL) 566 case <-streamDone: 567 } 568 } 569 570 func (s *ServerSuite) TestUpdateRequestResources(c *C) { 571 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 572 573 var err error 574 var req *envoy_api_v2.DiscoveryRequest 575 var resp *envoy_api_v2.DiscoveryResponse 576 var v uint64 577 var mod bool 578 579 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 580 defer cancel() 581 582 cache := NewCache() 583 mutator := NewAckingResourceMutatorWrapper(cache) 584 585 streamCtx, closeStream := context.WithCancel(ctx) 586 stream := NewMockStream(streamCtx, 1, 1, StreamTimeout, StreamTimeout) 587 defer stream.Close() 588 589 server := NewServer(map[string]*ResourceTypeConfiguration{typeURL: {Source: cache, AckObserver: mutator}}, 590 TestTimeout) 591 592 streamDone := make(chan struct{}) 593 594 // Run the server's stream handler concurrently. 595 go func() { 596 err := server.HandleRequestStream(ctx, stream, AnyTypeURL) 597 close(streamDone) 598 c.Check(err, IsNil) 599 }() 600 601 // Create version 2 with resources 0 and 1. 602 time.Sleep(CacheUpdateDelay) 603 v, mod, _ = cache.tx(typeURL, map[string]proto.Message{ 604 resources[0].Name: resources[0], 605 resources[1].Name: resources[1], 606 }, nil) 607 c.Assert(v, Equals, uint64(2)) 608 c.Assert(mod, Equals, true) 609 610 // Request resource 1. 611 req = &envoy_api_v2.DiscoveryRequest{ 612 TypeUrl: typeURL, 613 VersionInfo: "", 614 Node: nodes[node0], 615 ResourceNames: []string{resources[1].Name}, 616 ResponseNonce: "", 617 } 618 err = stream.SendRequest(req) 619 c.Assert(err, IsNil) 620 621 // Expecting a response with resource 1. 622 resp, err = stream.RecvResponse() 623 c.Assert(err, IsNil) 624 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 625 c.Assert(resp, ResponseMatches, "2", []proto.Message{resources[1]}, false, typeURL) 626 627 // Request the next version of resource 1. 628 req = &envoy_api_v2.DiscoveryRequest{ 629 TypeUrl: typeURL, 630 VersionInfo: resp.VersionInfo, // ACK the received version. 631 Node: nodes[node0], 632 ResourceNames: []string{resources[1].Name}, 633 ResponseNonce: resp.Nonce, 634 } 635 err = stream.SendRequest(req) 636 c.Assert(err, IsNil) 637 638 // Create version 3 with resource 0, 1 and 2. 639 time.Sleep(CacheUpdateDelay) 640 v, mod, _ = cache.Upsert(typeURL, resources[2].Name, resources[2]) 641 c.Assert(v, Equals, uint64(3)) 642 c.Assert(mod, Equals, true) 643 644 // Not expecting any response since resource 1 didn't change in version 3. 645 646 // Send an updated request for both resource 1 and 2. 647 req = &envoy_api_v2.DiscoveryRequest{ 648 TypeUrl: typeURL, 649 VersionInfo: resp.VersionInfo, // ACK the received version. 650 Node: nodes[node0], 651 ResourceNames: []string{resources[1].Name, resources[2].Name}, 652 ResponseNonce: resp.Nonce, 653 } 654 err = stream.SendRequest(req) 655 c.Assert(err, IsNil) 656 657 // Expecting a response with resources 1 and 2. 658 resp, err = stream.RecvResponse() 659 c.Assert(err, IsNil) 660 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 661 c.Assert(resp, ResponseMatches, "3", []proto.Message{resources[1], resources[2]}, false, typeURL) 662 663 // Close the stream. 664 closeStream() 665 666 select { 667 case <-ctx.Done(): 668 c.Errorf("HandleRequestStream(%v, %v, %v) took too long to return after stream was closed", "ctx", "stream", AnyTypeURL) 669 case <-streamDone: 670 } 671 } 672 673 func (s *ServerSuite) TestRequestStaleNonce(c *C) { 674 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 675 676 var err error 677 var req *envoy_api_v2.DiscoveryRequest 678 var resp *envoy_api_v2.DiscoveryResponse 679 var v uint64 680 var mod bool 681 682 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 683 defer cancel() 684 685 cache := NewCache() 686 mutator := NewAckingResourceMutatorWrapper(cache) 687 688 streamCtx, closeStream := context.WithCancel(ctx) 689 stream := NewMockStream(streamCtx, 1, 1, StreamTimeout, StreamTimeout) 690 defer stream.Close() 691 692 server := NewServer(map[string]*ResourceTypeConfiguration{typeURL: {Source: cache, AckObserver: mutator}}, 693 TestTimeout) 694 695 streamDone := make(chan struct{}) 696 697 // Run the server's stream handler concurrently. 698 go func() { 699 err := server.HandleRequestStream(ctx, stream, AnyTypeURL) 700 close(streamDone) 701 c.Check(err, IsNil) 702 }() 703 704 // Request all resources. 705 req = &envoy_api_v2.DiscoveryRequest{ 706 TypeUrl: typeURL, 707 VersionInfo: "", 708 Node: nodes[node0], 709 ResourceNames: nil, 710 ResponseNonce: "", 711 } 712 err = stream.SendRequest(req) 713 c.Assert(err, IsNil) 714 715 // Expecting an empty response. 716 resp, err = stream.RecvResponse() 717 c.Assert(err, IsNil) 718 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 719 c.Assert(resp, ResponseMatches, "1", nil, false, typeURL) 720 721 // Request the next version of resources. 722 req = &envoy_api_v2.DiscoveryRequest{ 723 TypeUrl: typeURL, 724 VersionInfo: resp.VersionInfo, // ACK the received version. 725 Node: nodes[node0], 726 ResourceNames: nil, 727 ResponseNonce: resp.Nonce, 728 } 729 err = stream.SendRequest(req) 730 c.Assert(err, IsNil) 731 732 // Create version 2 with resource 0. 733 time.Sleep(CacheUpdateDelay) 734 v, mod, _ = cache.Upsert(typeURL, resources[0].Name, resources[0]) 735 c.Assert(v, Equals, uint64(2)) 736 c.Assert(mod, Equals, true) 737 738 // Expecting a response with that resource. 739 resp, err = stream.RecvResponse() 740 c.Assert(err, IsNil) 741 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 742 c.Assert(resp, ResponseMatches, "2", []proto.Message{resources[0]}, false, typeURL) 743 744 // Create version 3 with resources 0 and 1. 745 // This time, update the cache before sending the request. 746 v, mod, _ = cache.Upsert(typeURL, resources[1].Name, resources[1]) 747 c.Assert(v, Equals, uint64(3)) 748 c.Assert(mod, Equals, true) 749 750 // Request the next version of resources, with a stale nonce. 751 req = &envoy_api_v2.DiscoveryRequest{ 752 TypeUrl: typeURL, 753 VersionInfo: resp.VersionInfo, // ACK the received version. 754 Node: nodes[node0], 755 ResourceNames: nil, 756 ResponseNonce: "0", 757 } 758 // Do not update the nonce. 759 err = stream.SendRequest(req) 760 c.Assert(err, IsNil) 761 762 // Expecting no response from the server. 763 764 // Resend the request with the correct nonce. 765 req = &envoy_api_v2.DiscoveryRequest{ 766 TypeUrl: typeURL, 767 VersionInfo: resp.VersionInfo, // ACK the received version. 768 Node: nodes[node0], 769 ResourceNames: nil, 770 ResponseNonce: resp.Nonce, 771 } 772 err = stream.SendRequest(req) 773 c.Assert(err, IsNil) 774 775 // Expecting a response with both resources. 776 resp, err = stream.RecvResponse() 777 c.Assert(err, IsNil) 778 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 779 c.Assert(resp, ResponseMatches, "3", []proto.Message{resources[0], resources[1]}, false, typeURL) 780 781 // Request the next version of resources. 782 req = &envoy_api_v2.DiscoveryRequest{ 783 TypeUrl: typeURL, 784 VersionInfo: resp.VersionInfo, // ACK the received version. 785 Node: nodes[node0], 786 ResourceNames: nil, 787 ResponseNonce: resp.Nonce, 788 } 789 err = stream.SendRequest(req) 790 c.Assert(err, IsNil) 791 792 // Create version 4 with resource 1. 793 time.Sleep(CacheUpdateDelay) 794 v, mod, _ = cache.Delete(typeURL, resources[0].Name) 795 c.Assert(v, Equals, uint64(4)) 796 c.Assert(mod, Equals, true) 797 798 // Expecting a response with that resource. 799 resp, err = stream.RecvResponse() 800 c.Assert(err, IsNil) 801 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 802 c.Assert(resp, ResponseMatches, "4", []proto.Message{resources[1]}, false, typeURL) 803 804 // Close the stream. 805 closeStream() 806 807 select { 808 case <-ctx.Done(): 809 c.Errorf("HandleRequestStream(%v, %v, %v) took too long to return after stream was closed", "ctx", "stream", AnyTypeURL) 810 case <-streamDone: 811 } 812 } 813 814 func (s *ServerSuite) TestNAck(c *C) { 815 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 816 817 var err error 818 var req *envoy_api_v2.DiscoveryRequest 819 var resp *envoy_api_v2.DiscoveryResponse 820 821 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 822 defer cancel() 823 wg := completion.NewWaitGroup(ctx) 824 825 cache := NewCache() 826 mutator := NewAckingResourceMutatorWrapper(cache) 827 828 streamCtx, closeStream := context.WithCancel(ctx) 829 stream := NewMockStream(streamCtx, 1, 1, StreamTimeout, StreamTimeout) 830 defer stream.Close() 831 832 server := NewServer(map[string]*ResourceTypeConfiguration{typeURL: {Source: cache, AckObserver: mutator}}, 833 TestTimeout) 834 835 streamDone := make(chan struct{}) 836 837 // Run the server's stream handler concurrently. 838 go func() { 839 err := server.HandleRequestStream(ctx, stream, AnyTypeURL) 840 close(streamDone) 841 c.Check(err, IsNil) 842 }() 843 844 // Request all resources. 845 req = &envoy_api_v2.DiscoveryRequest{ 846 TypeUrl: typeURL, 847 VersionInfo: "", 848 Node: nodes[node0], 849 ResourceNames: nil, 850 ResponseNonce: "", 851 } 852 err = stream.SendRequest(req) 853 c.Assert(err, IsNil) 854 855 // Expecting an empty response. 856 resp, err = stream.RecvResponse() 857 c.Assert(err, IsNil) 858 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 859 c.Assert(resp, ResponseMatches, "1", nil, false, typeURL) 860 861 // Request the next version of resources. 862 req = &envoy_api_v2.DiscoveryRequest{ 863 TypeUrl: typeURL, 864 VersionInfo: resp.VersionInfo, // ACK the received version. 865 Node: nodes[node0], 866 ResourceNames: nil, 867 ResponseNonce: resp.Nonce, 868 } 869 ackedVersion := resp.VersionInfo 870 err = stream.SendRequest(req) 871 c.Assert(err, IsNil) 872 873 // Create version 2 with resource 0. 874 time.Sleep(CacheUpdateDelay) 875 callback1, comp1 := newCompCallback() 876 mutator.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback1) 877 c.Assert(comp1, Not(IsCompleted)) 878 879 // Expecting a response with that resource. 880 resp, err = stream.RecvResponse() 881 c.Assert(err, IsNil) 882 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 883 c.Assert(resp, ResponseMatches, "2", []proto.Message{resources[0]}, false, typeURL) 884 885 // NACK the received version of resources. 886 req = &envoy_api_v2.DiscoveryRequest{ 887 TypeUrl: typeURL, 888 VersionInfo: ackedVersion, // NACK the received version. 889 Node: nodes[node0], 890 ResourceNames: nil, 891 ResponseNonce: resp.Nonce, 892 ErrorDetail: &status.Status{Message: "FAILFAIL"}, 893 } 894 err = stream.SendRequest(req) 895 c.Assert(err, IsNil) 896 897 // Create version 3 with resources 0 and 1. 898 time.Sleep(CacheUpdateDelay) 899 900 // NACK cancelled the wg, create a new one 901 wg = completion.NewWaitGroup(ctx) 902 callback2, comp2 := newCompCallback() 903 mutator.Upsert(typeURL, resources[1].Name, resources[1], []string{node0}, wg, callback2) 904 c.Assert(comp2, Not(IsCompleted)) 905 906 // Version 2 was NACKed by the last request, so comp1 must NOT be completed ever. 907 c.Assert(comp1, Not(IsCompleted)) 908 c.Assert(comp1.Err(), checker.DeepEquals, &ProxyError{Err: ErrNackReceived, Detail: "FAILFAIL"}) 909 910 // Expecting a response with both resources. 911 // Note that the stream should not have a message that repeats the previous one! 912 resp, err = stream.RecvResponse() 913 c.Assert(err, IsNil) 914 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 915 c.Assert(resp, ResponseMatches, "3", []proto.Message{resources[0], resources[1]}, false, typeURL) 916 917 c.Assert(comp1, Not(IsCompleted)) 918 c.Assert(comp2, Not(IsCompleted)) 919 920 // Request the next version of resources. 921 req = &envoy_api_v2.DiscoveryRequest{ 922 TypeUrl: typeURL, 923 VersionInfo: resp.VersionInfo, // ACK the received version. 924 Node: nodes[node0], 925 ResourceNames: nil, 926 ResponseNonce: resp.Nonce, 927 } 928 err = stream.SendRequest(req) 929 c.Assert(err, IsNil) 930 931 // Expecting no response. 932 933 time.Sleep(CacheUpdateDelay) 934 935 // comp2 was ACKed by the last request. 936 c.Assert(comp1, Not(IsCompleted)) 937 c.Assert(comp2, IsCompleted) 938 939 // Close the stream. 940 closeStream() 941 942 select { 943 case <-ctx.Done(): 944 c.Errorf("HandleRequestStream(%v, %v, %v) took too long to return after stream was closed", "ctx", "stream", AnyTypeURL) 945 case <-streamDone: 946 } 947 } 948 949 func (s *ServerSuite) TestNAckFromTheStart(c *C) { 950 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 951 952 var err error 953 var req *envoy_api_v2.DiscoveryRequest 954 var resp *envoy_api_v2.DiscoveryResponse 955 956 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 957 defer cancel() 958 wg := completion.NewWaitGroup(ctx) 959 960 cache := NewCache() 961 mutator := NewAckingResourceMutatorWrapper(cache) 962 963 streamCtx, closeStream := context.WithCancel(ctx) 964 stream := NewMockStream(streamCtx, 1, 1, StreamTimeout, StreamTimeout) 965 defer stream.Close() 966 967 server := NewServer(map[string]*ResourceTypeConfiguration{typeURL: {Source: cache, AckObserver: mutator}}, 968 TestTimeout) 969 970 streamDone := make(chan struct{}) 971 972 // Run the server's stream handler concurrently. 973 go func() { 974 err := server.HandleRequestStream(ctx, stream, AnyTypeURL) 975 close(streamDone) 976 c.Check(err, IsNil) 977 }() 978 979 // Request all resources. 980 req = &envoy_api_v2.DiscoveryRequest{ 981 TypeUrl: typeURL, 982 VersionInfo: "", 983 Node: nodes[node0], 984 ResourceNames: nil, 985 ResponseNonce: "", 986 } 987 err = stream.SendRequest(req) 988 c.Assert(err, IsNil) 989 990 // Expecting an empty response. 991 resp, err = stream.RecvResponse() 992 c.Assert(err, IsNil) 993 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 994 c.Assert(resp, ResponseMatches, "1", nil, false, typeURL) 995 996 // Create version 2 with resource 0. 997 time.Sleep(CacheUpdateDelay) 998 callback1, comp1 := newCompCallback() 999 mutator.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback1) 1000 c.Assert(comp1, Not(IsCompleted)) 1001 1002 // Request the next version of resources. 1003 req = &envoy_api_v2.DiscoveryRequest{ 1004 TypeUrl: typeURL, 1005 VersionInfo: "", // NACK all received versions. 1006 Node: nodes[node0], 1007 ResourceNames: nil, 1008 ResponseNonce: resp.Nonce, 1009 } 1010 err = stream.SendRequest(req) 1011 c.Assert(err, IsNil) 1012 1013 // Expecting a response with that resource. 1014 resp, err = stream.RecvResponse() 1015 c.Assert(err, IsNil) 1016 c.Assert(resp.Nonce, Equals, resp.VersionInfo) 1017 c.Assert(resp, ResponseMatches, "2", []proto.Message{resources[0]}, false, typeURL) 1018 1019 // NACK the received version of resources. 1020 req = &envoy_api_v2.DiscoveryRequest{ 1021 TypeUrl: typeURL, 1022 VersionInfo: "", // NACK all received versions. 1023 Node: nodes[node0], 1024 ResourceNames: nil, 1025 ResponseNonce: resp.Nonce, 1026 } 1027 err = stream.SendRequest(req) 1028 c.Assert(err, IsNil) 1029 1030 time.Sleep(CacheUpdateDelay) 1031 1032 // Version 2 was NACKed by the last request, so it must NOT be completed successfully. 1033 c.Assert(comp1, Not(IsCompleted)) 1034 // Version 2 did not have a callback, so the completion was completed with an error 1035 c.Assert(comp1.Err(), Not(IsNil)) 1036 c.Assert(comp1.Err(), checker.DeepEquals, &ProxyError{Err: ErrNackReceived}) 1037 1038 // NACK canceled the WaitGroup, create new one 1039 wg = completion.NewWaitGroup(ctx) 1040 1041 // Create version 3 with resources 0 and 1. 1042 callback2, comp2 := newCompCallback() 1043 mutator.Upsert(typeURL, resources[1].Name, resources[1], []string{node0}, wg, callback2) 1044 c.Assert(comp2, Not(IsCompleted)) 1045 1046 // Expecting a response with both resources. 1047 // Note that the stream should not have a message that repeats the previous one! 1048 resp, err = stream.RecvResponse() 1049 c.Assert(err, IsNil) 1050 c.Assert(resp, ResponseMatches, "3", []proto.Message{resources[0], resources[1]}, false, typeURL) 1051 c.Assert(resp.Nonce, Not(Equals), "") 1052 1053 c.Assert(comp2, Not(IsCompleted)) 1054 1055 // Request the next version of resources. 1056 req = &envoy_api_v2.DiscoveryRequest{ 1057 TypeUrl: typeURL, 1058 VersionInfo: resp.VersionInfo, // ACK the received version. 1059 Node: nodes[node0], 1060 ResourceNames: nil, 1061 ResponseNonce: resp.Nonce, 1062 } 1063 err = stream.SendRequest(req) 1064 c.Assert(err, IsNil) 1065 1066 // Expecting no response. 1067 1068 time.Sleep(CacheUpdateDelay) 1069 1070 // Version 3 was ACKed by the last request. 1071 c.Assert(comp2, IsCompleted) 1072 1073 // Close the stream. 1074 closeStream() 1075 1076 select { 1077 case <-ctx.Done(): 1078 c.Errorf("HandleRequestStream(%v, %v, %v) took too long to return after stream was closed", "ctx", "stream", AnyTypeURL) 1079 case <-streamDone: 1080 } 1081 } 1082 1083 func (s *ServerSuite) TestRequestHighVersionFromTheStart(c *C) { 1084 typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration" 1085 1086 var err error 1087 var req *envoy_api_v2.DiscoveryRequest 1088 var resp *envoy_api_v2.DiscoveryResponse 1089 1090 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 1091 defer cancel() 1092 wg := completion.NewWaitGroup(ctx) 1093 1094 cache := NewCache() 1095 mutator := NewAckingResourceMutatorWrapper(cache) 1096 1097 streamCtx, closeStream := context.WithCancel(ctx) 1098 stream := NewMockStream(streamCtx, 1, 1, StreamTimeout, StreamTimeout) 1099 defer stream.Close() 1100 1101 server := NewServer(map[string]*ResourceTypeConfiguration{typeURL: {Source: cache, AckObserver: mutator}}, 1102 TestTimeout) 1103 1104 streamDone := make(chan struct{}) 1105 1106 // Run the server's stream handler concurrently. 1107 go func() { 1108 err := server.HandleRequestStream(ctx, stream, AnyTypeURL) 1109 close(streamDone) 1110 c.Check(err, IsNil) 1111 }() 1112 1113 // Create version 2 with resource 0. 1114 time.Sleep(CacheUpdateDelay) 1115 callback1, comp1 := newCompCallback() 1116 mutator.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback1) 1117 c.Assert(comp1, Not(IsCompleted)) 1118 1119 // Request all resources, with a version higher than the version currently 1120 // in Cilium's cache. This happens after the server restarts but the 1121 // xDS client survives and continues to request the same version. 1122 req = &envoy_api_v2.DiscoveryRequest{ 1123 TypeUrl: typeURL, 1124 VersionInfo: "64", 1125 Node: nodes[node0], 1126 ResourceNames: nil, 1127 ResponseNonce: "64", 1128 } 1129 err = stream.SendRequest(req) 1130 c.Assert(err, IsNil) 1131 1132 // Expecting a response with that resource, and an updated version. 1133 resp, err = stream.RecvResponse() 1134 c.Assert(err, IsNil) 1135 c.Assert(resp, ResponseMatches, "65", []proto.Message{resources[0]}, false, typeURL) 1136 c.Assert(resp.Nonce, Not(Equals), "") 1137 1138 // Close the stream. 1139 closeStream() 1140 1141 select { 1142 case <-ctx.Done(): 1143 c.Errorf("HandleRequestStream(%v, %v, %v) took too long to return after stream was closed", "ctx", "stream", AnyTypeURL) 1144 case <-streamDone: 1145 } 1146 }