google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/tests/lds_watchers_test.go (about) 1 /* 2 * 3 * Copyright 2022 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package xdsclient_test 20 21 import ( 22 "context" 23 "fmt" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/envoyproxy/go-control-plane/pkg/wellknown" 29 "github.com/google/go-cmp/cmp" 30 "github.com/google/go-cmp/cmp/cmpopts" 31 "github.com/google/uuid" 32 "google.golang.org/grpc/internal/grpcsync" 33 "google.golang.org/grpc/internal/grpctest" 34 "google.golang.org/grpc/internal/testutils" 35 "google.golang.org/grpc/internal/testutils/xds/e2e" 36 xdstestutils "google.golang.org/grpc/xds/internal/testutils" 37 "google.golang.org/grpc/xds/internal/xdsclient" 38 "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" 39 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" 40 41 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 42 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 43 v3routerpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3" 44 v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" 45 v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 46 47 _ "google.golang.org/grpc/xds" // To ensure internal.NewXDSResolverWithConfigForTesting is set. 48 _ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter. 49 ) 50 51 type s struct { 52 grpctest.Tester 53 } 54 55 func Test(t *testing.T) { 56 grpctest.RunSubTests(t, s{}) 57 } 58 59 const ( 60 defaultTestWatchExpiryTimeout = 500 * time.Millisecond 61 defaultTestIdleAuthorityTimeout = 50 * time.Millisecond 62 defaultTestTimeout = 5 * time.Second 63 defaultTestShortTimeout = 10 * time.Millisecond // For events expected to *not* happen. 64 65 ldsName = "xdsclient-test-lds-resource" 66 rdsName = "xdsclient-test-rds-resource" 67 cdsName = "xdsclient-test-cds-resource" 68 edsName = "xdsclient-test-eds-resource" 69 ldsNameNewStyle = "xdstp:///envoy.config.listener.v3.Listener/xdsclient-test-lds-resource" 70 rdsNameNewStyle = "xdstp:///envoy.config.route.v3.RouteConfiguration/xdsclient-test-rds-resource" 71 cdsNameNewStyle = "xdstp:///envoy.config.cluster.v3.Cluster/xdsclient-test-cds-resource" 72 edsNameNewStyle = "xdstp:///envoy.config.endpoint.v3.ClusterLoadAssignment/xdsclient-test-eds-resource" 73 ) 74 75 type noopListenerWatcher struct{} 76 77 func (noopListenerWatcher) OnUpdate(update *xdsresource.ListenerResourceData) {} 78 func (noopListenerWatcher) OnError(err error) {} 79 func (noopListenerWatcher) OnResourceDoesNotExist() {} 80 81 type listenerUpdateErrTuple struct { 82 update xdsresource.ListenerUpdate 83 err error 84 } 85 86 type listenerWatcher struct { 87 updateCh *testutils.Channel 88 } 89 90 func newListenerWatcher() *listenerWatcher { 91 return &listenerWatcher{updateCh: testutils.NewChannel()} 92 } 93 94 func (cw *listenerWatcher) OnUpdate(update *xdsresource.ListenerResourceData) { 95 cw.updateCh.Send(listenerUpdateErrTuple{update: update.Resource}) 96 } 97 98 func (cw *listenerWatcher) OnError(err error) { 99 // When used with a go-control-plane management server that continuously 100 // resends resources which are NACKed by the xDS client, using a `Replace()` 101 // here and in OnResourceDoesNotExist() simplifies tests which will have 102 // access to the most recently received error. 103 cw.updateCh.Replace(listenerUpdateErrTuple{err: err}) 104 } 105 106 func (cw *listenerWatcher) OnResourceDoesNotExist() { 107 cw.updateCh.Replace(listenerUpdateErrTuple{err: xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, "Listener not found in received response")}) 108 } 109 110 // badListenerResource returns a listener resource for the given name which does 111 // not contain the `RouteSpecifier` field in the HTTPConnectionManager, and 112 // hence is expected to be NACKed by the client. 113 func badListenerResource(t *testing.T, name string) *v3listenerpb.Listener { 114 hcm := testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{ 115 HttpFilters: []*v3httppb.HttpFilter{e2e.HTTPFilter("router", &v3routerpb.Router{})}, 116 }) 117 return &v3listenerpb.Listener{ 118 Name: name, 119 ApiListener: &v3listenerpb.ApiListener{ApiListener: hcm}, 120 FilterChains: []*v3listenerpb.FilterChain{{ 121 Name: "filter-chain-name", 122 Filters: []*v3listenerpb.Filter{{ 123 Name: wellknown.HTTPConnectionManager, 124 ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: hcm}, 125 }}, 126 }}, 127 } 128 } 129 130 // xdsClient is expected to produce an error containing this string when an 131 // update is received containing a listener created using `badListenerResource`. 132 const wantListenerNACKErr = "no RouteSpecifier" 133 134 // verifyNoListenerUpdate verifies that no listener update is received on the 135 // provided update channel, and returns an error if an update is received. 136 // 137 // A very short deadline is used while waiting for the update, as this function 138 // is intended to be used when an update is not expected. 139 func verifyNoListenerUpdate(ctx context.Context, updateCh *testutils.Channel) error { 140 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 141 defer sCancel() 142 if u, err := updateCh.Receive(sCtx); err != context.DeadlineExceeded { 143 return fmt.Errorf("unexpected ListenerUpdate: %v", u) 144 } 145 return nil 146 } 147 148 // verifyListenerUpdate waits for an update to be received on the provided 149 // update channel and verifies that it matches the expected update. 150 // 151 // Returns an error if no update is received before the context deadline expires 152 // or the received update does not match the expected one. 153 func verifyListenerUpdate(ctx context.Context, updateCh *testutils.Channel, wantUpdate listenerUpdateErrTuple) error { 154 u, err := updateCh.Receive(ctx) 155 if err != nil { 156 return fmt.Errorf("timeout when waiting for a listener resource from the management server: %v", err) 157 } 158 got := u.(listenerUpdateErrTuple) 159 if wantUpdate.err != nil { 160 if gotType, wantType := xdsresource.ErrType(got.err), xdsresource.ErrType(wantUpdate.err); gotType != wantType { 161 return fmt.Errorf("received update with error type %v, want %v", gotType, wantType) 162 } 163 } 164 cmpOpts := []cmp.Option{ 165 cmpopts.EquateEmpty(), 166 cmpopts.IgnoreFields(xdsresource.HTTPFilter{}, "Filter", "Config"), 167 cmpopts.IgnoreFields(xdsresource.ListenerUpdate{}, "Raw"), 168 } 169 if diff := cmp.Diff(wantUpdate.update, got.update, cmpOpts...); diff != "" { 170 return fmt.Errorf("received unepected diff in the listener resource update: (-want, got):\n%s", diff) 171 } 172 return nil 173 } 174 175 // TestLDSWatch covers the case where a single watcher exists for a single 176 // listener resource. The test verifies the following scenarios: 177 // 1. An update from the management server containing the resource being 178 // watched should result in the invocation of the watch callback. 179 // 2. An update from the management server containing a resource *not* being 180 // watched should not result in the invocation of the watch callback. 181 // 3. After the watch is cancelled, an update from the management server 182 // containing the resource that was being watched should not result in the 183 // invocation of the watch callback. 184 // 185 // The test is run for old and new style names. 186 func (s) TestLDSWatch(t *testing.T) { 187 tests := []struct { 188 desc string 189 resourceName string 190 watchedResource *v3listenerpb.Listener // The resource being watched. 191 updatedWatchedResource *v3listenerpb.Listener // The watched resource after an update. 192 notWatchedResource *v3listenerpb.Listener // A resource which is not being watched. 193 wantUpdate listenerUpdateErrTuple 194 }{ 195 { 196 desc: "old style resource", 197 resourceName: ldsName, 198 watchedResource: e2e.DefaultClientListener(ldsName, rdsName), 199 updatedWatchedResource: e2e.DefaultClientListener(ldsName, "new-rds-resource"), 200 notWatchedResource: e2e.DefaultClientListener("unsubscribed-lds-resource", rdsName), 201 wantUpdate: listenerUpdateErrTuple{ 202 update: xdsresource.ListenerUpdate{ 203 RouteConfigName: rdsName, 204 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 205 }, 206 }, 207 }, 208 { 209 desc: "new style resource", 210 resourceName: ldsNameNewStyle, 211 watchedResource: e2e.DefaultClientListener(ldsNameNewStyle, rdsNameNewStyle), 212 updatedWatchedResource: e2e.DefaultClientListener(ldsNameNewStyle, "new-rds-resource"), 213 notWatchedResource: e2e.DefaultClientListener("unsubscribed-lds-resource", rdsNameNewStyle), 214 wantUpdate: listenerUpdateErrTuple{ 215 update: xdsresource.ListenerUpdate{ 216 RouteConfigName: rdsNameNewStyle, 217 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 218 }, 219 }, 220 }, 221 } 222 223 for _, test := range tests { 224 t.Run(test.desc, func(t *testing.T) { 225 mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{}) 226 defer cleanup() 227 228 // Create an xDS client with the above bootstrap contents. 229 client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) 230 if err != nil { 231 t.Fatalf("Failed to create xDS client: %v", err) 232 } 233 defer close() 234 235 // Register a watch for a listener resource and have the watch 236 // callback push the received update on to a channel. 237 lw := newListenerWatcher() 238 ldsCancel := xdsresource.WatchListener(client, test.resourceName, lw) 239 240 // Configure the management server to return a single listener 241 // resource, corresponding to the one we registered a watch for. 242 resources := e2e.UpdateOptions{ 243 NodeID: nodeID, 244 Listeners: []*v3listenerpb.Listener{test.watchedResource}, 245 SkipValidation: true, 246 } 247 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 248 defer cancel() 249 if err := mgmtServer.Update(ctx, resources); err != nil { 250 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 251 } 252 253 // Verify the contents of the received update. 254 if err := verifyListenerUpdate(ctx, lw.updateCh, test.wantUpdate); err != nil { 255 t.Fatal(err) 256 } 257 258 // Configure the management server to return an additional listener 259 // resource, one that we are not interested in. 260 resources = e2e.UpdateOptions{ 261 NodeID: nodeID, 262 Listeners: []*v3listenerpb.Listener{test.watchedResource, test.notWatchedResource}, 263 SkipValidation: true, 264 } 265 if err := mgmtServer.Update(ctx, resources); err != nil { 266 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 267 } 268 if err := verifyNoListenerUpdate(ctx, lw.updateCh); err != nil { 269 t.Fatal(err) 270 } 271 272 // Cancel the watch and update the resource corresponding to the original 273 // watch. Ensure that the cancelled watch callback is not invoked. 274 ldsCancel() 275 resources = e2e.UpdateOptions{ 276 NodeID: nodeID, 277 Listeners: []*v3listenerpb.Listener{test.updatedWatchedResource, test.notWatchedResource}, 278 SkipValidation: true, 279 } 280 if err := mgmtServer.Update(ctx, resources); err != nil { 281 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 282 } 283 if err := verifyNoListenerUpdate(ctx, lw.updateCh); err != nil { 284 t.Fatal(err) 285 } 286 }) 287 } 288 } 289 290 // TestLDSWatch_TwoWatchesForSameResourceName covers the case where two watchers 291 // exist for a single listener resource. The test verifies the following 292 // scenarios: 293 // 1. An update from the management server containing the resource being 294 // watched should result in the invocation of both watch callbacks. 295 // 2. After one of the watches is cancelled, a redundant update from the 296 // management server should not result in the invocation of either of the 297 // watch callbacks. 298 // 3. An update from the management server containing the resource being 299 // watched should result in the invocation of the un-cancelled watch 300 // callback. 301 // 302 // The test is run for old and new style names. 303 func (s) TestLDSWatch_TwoWatchesForSameResourceName(t *testing.T) { 304 tests := []struct { 305 desc string 306 resourceName string 307 watchedResource *v3listenerpb.Listener // The resource being watched. 308 updatedWatchedResource *v3listenerpb.Listener // The watched resource after an update. 309 wantUpdateV1 listenerUpdateErrTuple 310 wantUpdateV2 listenerUpdateErrTuple 311 }{ 312 { 313 desc: "old style resource", 314 resourceName: ldsName, 315 watchedResource: e2e.DefaultClientListener(ldsName, rdsName), 316 updatedWatchedResource: e2e.DefaultClientListener(ldsName, "new-rds-resource"), 317 wantUpdateV1: listenerUpdateErrTuple{ 318 update: xdsresource.ListenerUpdate{ 319 RouteConfigName: rdsName, 320 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 321 }, 322 }, 323 wantUpdateV2: listenerUpdateErrTuple{ 324 update: xdsresource.ListenerUpdate{ 325 RouteConfigName: "new-rds-resource", 326 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 327 }, 328 }, 329 }, 330 { 331 desc: "new style resource", 332 resourceName: ldsNameNewStyle, 333 watchedResource: e2e.DefaultClientListener(ldsNameNewStyle, rdsNameNewStyle), 334 updatedWatchedResource: e2e.DefaultClientListener(ldsNameNewStyle, "new-rds-resource"), 335 wantUpdateV1: listenerUpdateErrTuple{ 336 update: xdsresource.ListenerUpdate{ 337 RouteConfigName: rdsNameNewStyle, 338 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 339 }, 340 }, 341 wantUpdateV2: listenerUpdateErrTuple{ 342 update: xdsresource.ListenerUpdate{ 343 RouteConfigName: "new-rds-resource", 344 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 345 }, 346 }, 347 }, 348 } 349 350 for _, test := range tests { 351 t.Run(test.desc, func(t *testing.T) { 352 mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{}) 353 defer cleanup() 354 355 // Create an xDS client with the above bootstrap contents. 356 client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) 357 if err != nil { 358 t.Fatalf("Failed to create xDS client: %v", err) 359 } 360 defer close() 361 362 // Register two watches for the same listener resource and have the 363 // callbacks push the received updates on to a channel. 364 lw1 := newListenerWatcher() 365 ldsCancel1 := xdsresource.WatchListener(client, test.resourceName, lw1) 366 defer ldsCancel1() 367 lw2 := newListenerWatcher() 368 ldsCancel2 := xdsresource.WatchListener(client, test.resourceName, lw2) 369 370 // Configure the management server to return a single listener 371 // resource, corresponding to the one we registered watches for. 372 resources := e2e.UpdateOptions{ 373 NodeID: nodeID, 374 Listeners: []*v3listenerpb.Listener{test.watchedResource}, 375 SkipValidation: true, 376 } 377 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 378 defer cancel() 379 if err := mgmtServer.Update(ctx, resources); err != nil { 380 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 381 } 382 383 // Verify the contents of the received update. 384 if err := verifyListenerUpdate(ctx, lw1.updateCh, test.wantUpdateV1); err != nil { 385 t.Fatal(err) 386 } 387 if err := verifyListenerUpdate(ctx, lw2.updateCh, test.wantUpdateV1); err != nil { 388 t.Fatal(err) 389 } 390 391 // Cancel the second watch and force the management server to push a 392 // redundant update for the resource being watched. Neither of the 393 // two watch callbacks should be invoked. 394 ldsCancel2() 395 if err := mgmtServer.Update(ctx, resources); err != nil { 396 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 397 } 398 if err := verifyNoListenerUpdate(ctx, lw1.updateCh); err != nil { 399 t.Fatal(err) 400 } 401 if err := verifyNoListenerUpdate(ctx, lw2.updateCh); err != nil { 402 t.Fatal(err) 403 } 404 405 // Update to the resource being watched. The un-cancelled callback 406 // should be invoked while the cancelled one should not be. 407 resources = e2e.UpdateOptions{ 408 NodeID: nodeID, 409 Listeners: []*v3listenerpb.Listener{test.updatedWatchedResource}, 410 SkipValidation: true, 411 } 412 if err := mgmtServer.Update(ctx, resources); err != nil { 413 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 414 } 415 if err := verifyListenerUpdate(ctx, lw1.updateCh, test.wantUpdateV2); err != nil { 416 t.Fatal(err) 417 } 418 if err := verifyNoListenerUpdate(ctx, lw2.updateCh); err != nil { 419 t.Fatal(err) 420 } 421 }) 422 } 423 } 424 425 // TestLDSWatch_ThreeWatchesForDifferentResourceNames covers the case with three 426 // watchers (two watchers for one resource, and the third watcher for another 427 // resource), exist across two listener resources. The test verifies that an 428 // update from the management server containing both resources results in the 429 // invocation of all watch callbacks. 430 // 431 // The test is run with both old and new style names. 432 func (s) TestLDSWatch_ThreeWatchesForDifferentResourceNames(t *testing.T) { 433 mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{}) 434 defer cleanup() 435 436 // Create an xDS client with the above bootstrap contents. 437 client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) 438 if err != nil { 439 t.Fatalf("Failed to create xDS client: %v", err) 440 } 441 defer close() 442 443 // Register two watches for the same listener resource and have the 444 // callbacks push the received updates on to a channel. 445 lw1 := newListenerWatcher() 446 ldsCancel1 := xdsresource.WatchListener(client, ldsName, lw1) 447 defer ldsCancel1() 448 lw2 := newListenerWatcher() 449 ldsCancel2 := xdsresource.WatchListener(client, ldsName, lw2) 450 defer ldsCancel2() 451 452 // Register the third watch for a different listener resource. 453 lw3 := newListenerWatcher() 454 ldsCancel3 := xdsresource.WatchListener(client, ldsNameNewStyle, lw3) 455 defer ldsCancel3() 456 457 // Configure the management server to return two listener resources, 458 // corresponding to the registered watches. 459 resources := e2e.UpdateOptions{ 460 NodeID: nodeID, 461 Listeners: []*v3listenerpb.Listener{ 462 e2e.DefaultClientListener(ldsName, rdsName), 463 e2e.DefaultClientListener(ldsNameNewStyle, rdsName), 464 }, 465 SkipValidation: true, 466 } 467 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 468 defer cancel() 469 if err := mgmtServer.Update(ctx, resources); err != nil { 470 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 471 } 472 473 // Verify the contents of the received update for the all watchers. The two 474 // resources returned differ only in the resource name. Therefore the 475 // expected update is the same for all the watchers. 476 wantUpdate := listenerUpdateErrTuple{ 477 update: xdsresource.ListenerUpdate{ 478 RouteConfigName: rdsName, 479 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 480 }, 481 } 482 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate); err != nil { 483 t.Fatal(err) 484 } 485 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 486 t.Fatal(err) 487 } 488 if err := verifyListenerUpdate(ctx, lw3.updateCh, wantUpdate); err != nil { 489 t.Fatal(err) 490 } 491 } 492 493 // TestLDSWatch_ResourceCaching covers the case where a watch is registered for 494 // a resource which is already present in the cache. The test verifies that the 495 // watch callback is invoked with the contents from the cache, instead of a 496 // request being sent to the management server. 497 func (s) TestLDSWatch_ResourceCaching(t *testing.T) { 498 firstRequestReceived := false 499 firstAckReceived := grpcsync.NewEvent() 500 secondRequestReceived := grpcsync.NewEvent() 501 502 mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{ 503 OnStreamRequest: func(id int64, req *v3discoverypb.DiscoveryRequest) error { 504 // The first request has an empty version string. 505 if !firstRequestReceived && req.GetVersionInfo() == "" { 506 firstRequestReceived = true 507 return nil 508 } 509 // The first ack has a non-empty version string. 510 if !firstAckReceived.HasFired() && req.GetVersionInfo() != "" { 511 firstAckReceived.Fire() 512 return nil 513 } 514 // Any requests after the first request and ack, are not expected. 515 secondRequestReceived.Fire() 516 return nil 517 }, 518 }) 519 defer cleanup() 520 521 // Create an xDS client with the above bootstrap contents. 522 client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) 523 if err != nil { 524 t.Fatalf("Failed to create xDS client: %v", err) 525 } 526 defer close() 527 528 // Register a watch for a listener resource and have the watch 529 // callback push the received update on to a channel. 530 lw1 := newListenerWatcher() 531 ldsCancel1 := xdsresource.WatchListener(client, ldsName, lw1) 532 defer ldsCancel1() 533 534 // Configure the management server to return a single listener 535 // resource, corresponding to the one we registered a watch for. 536 resources := e2e.UpdateOptions{ 537 NodeID: nodeID, 538 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(ldsName, rdsName)}, 539 SkipValidation: true, 540 } 541 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 542 defer cancel() 543 if err := mgmtServer.Update(ctx, resources); err != nil { 544 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 545 } 546 547 // Verify the contents of the received update. 548 wantUpdate := listenerUpdateErrTuple{ 549 update: xdsresource.ListenerUpdate{ 550 RouteConfigName: rdsName, 551 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 552 }, 553 } 554 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate); err != nil { 555 t.Fatal(err) 556 } 557 select { 558 case <-ctx.Done(): 559 t.Fatal("timeout when waiting for receipt of ACK at the management server") 560 case <-firstAckReceived.Done(): 561 } 562 563 // Register another watch for the same resource. This should get the update 564 // from the cache. 565 lw2 := newListenerWatcher() 566 ldsCancel2 := xdsresource.WatchListener(client, ldsName, lw2) 567 defer ldsCancel2() 568 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 569 t.Fatal(err) 570 } 571 // No request should get sent out as part of this watch. 572 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 573 defer sCancel() 574 select { 575 case <-sCtx.Done(): 576 case <-secondRequestReceived.Done(): 577 t.Fatal("xdsClient sent out request instead of using update from cache") 578 } 579 } 580 581 // TestLDSWatch_ExpiryTimerFiresBeforeResponse tests the case where the client 582 // does not receive an LDS response for the request that it sends. The test 583 // verifies that the watch callback is invoked with an error once the 584 // watchExpiryTimer fires. 585 func (s) TestLDSWatch_ExpiryTimerFiresBeforeResponse(t *testing.T) { 586 mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{}) 587 if err != nil { 588 t.Fatalf("Failed to spin up the xDS management server: %v", err) 589 } 590 defer mgmtServer.Stop() 591 592 client, close, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{ 593 XDSServer: xdstestutils.ServerConfigForAddress(t, mgmtServer.Address), 594 NodeProto: &v3corepb.Node{}, 595 }, defaultTestWatchExpiryTimeout, time.Duration(0)) 596 if err != nil { 597 t.Fatalf("failed to create xds client: %v", err) 598 } 599 defer close() 600 601 // Register a watch for a resource which is expected to fail with an error 602 // after the watch expiry timer fires. 603 lw := newListenerWatcher() 604 ldsCancel := xdsresource.WatchListener(client, ldsName, lw) 605 defer ldsCancel() 606 607 // Wait for the watch expiry timer to fire. 608 <-time.After(defaultTestWatchExpiryTimeout) 609 610 // Verify that an empty update with the expected error is received. 611 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 612 defer cancel() 613 wantErr := xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, "") 614 if err := verifyListenerUpdate(ctx, lw.updateCh, listenerUpdateErrTuple{err: wantErr}); err != nil { 615 t.Fatal(err) 616 } 617 } 618 619 // TestLDSWatch_ValidResponseCancelsExpiryTimerBehavior tests the case where the 620 // client receives a valid LDS response for the request that it sends. The test 621 // verifies that the behavior associated with the expiry timer (i.e, callback 622 // invocation with error) does not take place. 623 func (s) TestLDSWatch_ValidResponseCancelsExpiryTimerBehavior(t *testing.T) { 624 mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{}) 625 if err != nil { 626 t.Fatalf("Failed to spin up the xDS management server: %v", err) 627 } 628 defer mgmtServer.Stop() 629 630 // Create an xDS client talking to the above management server. 631 nodeID := uuid.New().String() 632 client, close, err := xdsclient.NewWithConfigForTesting(&bootstrap.Config{ 633 XDSServer: xdstestutils.ServerConfigForAddress(t, mgmtServer.Address), 634 NodeProto: &v3corepb.Node{Id: nodeID}, 635 }, defaultTestWatchExpiryTimeout, time.Duration(0)) 636 if err != nil { 637 t.Fatalf("failed to create xds client: %v", err) 638 } 639 defer close() 640 641 // Register a watch for a listener resource and have the watch 642 // callback push the received update on to a channel. 643 lw := newListenerWatcher() 644 ldsCancel := xdsresource.WatchListener(client, ldsName, lw) 645 defer ldsCancel() 646 647 // Configure the management server to return a single listener 648 // resource, corresponding to the one we registered a watch for. 649 resources := e2e.UpdateOptions{ 650 NodeID: nodeID, 651 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(ldsName, rdsName)}, 652 SkipValidation: true, 653 } 654 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 655 defer cancel() 656 if err := mgmtServer.Update(ctx, resources); err != nil { 657 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 658 } 659 660 // Verify the contents of the received update. 661 wantUpdate := listenerUpdateErrTuple{ 662 update: xdsresource.ListenerUpdate{ 663 RouteConfigName: rdsName, 664 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 665 }, 666 } 667 if err := verifyListenerUpdate(ctx, lw.updateCh, wantUpdate); err != nil { 668 t.Fatal(err) 669 } 670 671 // Wait for the watch expiry timer to fire, and verify that the callback is 672 // not invoked. 673 <-time.After(defaultTestWatchExpiryTimeout) 674 if err := verifyNoListenerUpdate(ctx, lw.updateCh); err != nil { 675 t.Fatal(err) 676 } 677 } 678 679 // TestLDSWatch_ResourceRemoved covers the cases where a resource being watched 680 // is removed from the management server. The test verifies the following 681 // scenarios: 682 // 1. Removing a resource should trigger the watch callback with a resource 683 // removed error. It should not trigger the watch callback for an unrelated 684 // resource. 685 // 2. An update to another resource should result in the invocation of the watch 686 // callback associated with that resource. It should not result in the 687 // invocation of the watch callback associated with the deleted resource. 688 // 689 // The test is run with both old and new style names. 690 func (s) TestLDSWatch_ResourceRemoved(t *testing.T) { 691 mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{}) 692 defer cleanup() 693 694 // Create an xDS client with the above bootstrap contents. 695 client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) 696 if err != nil { 697 t.Fatalf("Failed to create xDS client: %v", err) 698 } 699 defer close() 700 701 // Register two watches for two listener resources and have the 702 // callbacks push the received updates on to a channel. 703 resourceName1 := ldsName 704 lw1 := newListenerWatcher() 705 ldsCancel1 := xdsresource.WatchListener(client, resourceName1, lw1) 706 defer ldsCancel1() 707 708 resourceName2 := ldsNameNewStyle 709 lw2 := newListenerWatcher() 710 ldsCancel2 := xdsresource.WatchListener(client, resourceName2, lw2) 711 defer ldsCancel2() 712 713 // Configure the management server to return two listener resources, 714 // corresponding to the registered watches. 715 resources := e2e.UpdateOptions{ 716 NodeID: nodeID, 717 Listeners: []*v3listenerpb.Listener{ 718 e2e.DefaultClientListener(resourceName1, rdsName), 719 e2e.DefaultClientListener(resourceName2, rdsName), 720 }, 721 SkipValidation: true, 722 } 723 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 724 defer cancel() 725 if err := mgmtServer.Update(ctx, resources); err != nil { 726 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 727 } 728 729 // Verify the contents of the received update for both watchers. The two 730 // resources returned differ only in the resource name. Therefore the 731 // expected update is the same for both watchers. 732 wantUpdate := listenerUpdateErrTuple{ 733 update: xdsresource.ListenerUpdate{ 734 RouteConfigName: rdsName, 735 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 736 }, 737 } 738 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate); err != nil { 739 t.Fatal(err) 740 } 741 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 742 t.Fatal(err) 743 } 744 745 // Remove the first listener resource on the management server. 746 resources = e2e.UpdateOptions{ 747 NodeID: nodeID, 748 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(resourceName2, rdsName)}, 749 SkipValidation: true, 750 } 751 if err := mgmtServer.Update(ctx, resources); err != nil { 752 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 753 } 754 755 // The first watcher should receive a resource removed error, while the 756 // second watcher should not see an update. 757 if err := verifyListenerUpdate(ctx, lw1.updateCh, listenerUpdateErrTuple{ 758 err: xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, ""), 759 }); err != nil { 760 t.Fatal(err) 761 } 762 if err := verifyNoListenerUpdate(ctx, lw2.updateCh); err != nil { 763 t.Fatal(err) 764 } 765 766 // Update the second listener resource on the management server. The first 767 // watcher should not see an update, while the second watcher should. 768 resources = e2e.UpdateOptions{ 769 NodeID: nodeID, 770 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(resourceName2, "new-rds-resource")}, 771 SkipValidation: true, 772 } 773 if err := mgmtServer.Update(ctx, resources); err != nil { 774 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 775 } 776 if err := verifyNoListenerUpdate(ctx, lw1.updateCh); err != nil { 777 t.Fatal(err) 778 } 779 wantUpdate = listenerUpdateErrTuple{ 780 update: xdsresource.ListenerUpdate{ 781 RouteConfigName: "new-rds-resource", 782 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 783 }, 784 } 785 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 786 t.Fatal(err) 787 } 788 } 789 790 // TestLDSWatch_NACKError covers the case where an update from the management 791 // server is NACK'ed by the xdsclient. The test verifies that the error is 792 // propagated to the watcher. 793 func (s) TestLDSWatch_NACKError(t *testing.T) { 794 mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{}) 795 defer cleanup() 796 797 // Create an xDS client with the above bootstrap contents. 798 client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) 799 if err != nil { 800 t.Fatalf("Failed to create xDS client: %v", err) 801 } 802 defer close() 803 804 // Register a watch for a listener resource and have the watch 805 // callback push the received update on to a channel. 806 lw := newListenerWatcher() 807 ldsCancel := xdsresource.WatchListener(client, ldsName, lw) 808 defer ldsCancel() 809 810 // Configure the management server to return a single listener resource 811 // which is expected to be NACKed by the client. 812 resources := e2e.UpdateOptions{ 813 NodeID: nodeID, 814 Listeners: []*v3listenerpb.Listener{badListenerResource(t, ldsName)}, 815 SkipValidation: true, 816 } 817 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 818 defer cancel() 819 if err := mgmtServer.Update(ctx, resources); err != nil { 820 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 821 } 822 823 // Verify that the expected error is propagated to the watcher. 824 u, err := lw.updateCh.Receive(ctx) 825 if err != nil { 826 t.Fatalf("timeout when waiting for a listener resource from the management server: %v", err) 827 } 828 gotErr := u.(listenerUpdateErrTuple).err 829 if gotErr == nil || !strings.Contains(gotErr.Error(), wantListenerNACKErr) { 830 t.Fatalf("update received with error: %v, want %q", gotErr, wantListenerNACKErr) 831 } 832 } 833 834 // TestLDSWatch_PartialValid covers the case where a response from the 835 // management server contains both valid and invalid resources and is expected 836 // to be NACK'ed by the xdsclient. The test verifies that watchers corresponding 837 // to the valid resource receive the update, while watchers corresponding to the 838 // invalid resource receive an error. 839 func (s) TestLDSWatch_PartialValid(t *testing.T) { 840 mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{}) 841 defer cleanup() 842 843 // Create an xDS client with the above bootstrap contents. 844 client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) 845 if err != nil { 846 t.Fatalf("Failed to create xDS client: %v", err) 847 } 848 defer close() 849 850 // Register two watches for listener resources. The first watch is expected 851 // to receive an error because the received resource is NACKed. The second 852 // watch is expected to get a good update. 853 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 854 defer cancel() 855 badResourceName := ldsName 856 lw1 := newListenerWatcher() 857 ldsCancel1 := xdsresource.WatchListener(client, badResourceName, lw1) 858 defer ldsCancel1() 859 goodResourceName := ldsNameNewStyle 860 lw2 := newListenerWatcher() 861 ldsCancel2 := xdsresource.WatchListener(client, goodResourceName, lw2) 862 defer ldsCancel2() 863 864 // Configure the management server with two listener resources. One of these 865 // is a bad resource causing the update to be NACKed. 866 resources := e2e.UpdateOptions{ 867 NodeID: nodeID, 868 Listeners: []*v3listenerpb.Listener{ 869 badListenerResource(t, badResourceName), 870 e2e.DefaultClientListener(goodResourceName, rdsName), 871 }, 872 SkipValidation: true, 873 } 874 if err := mgmtServer.Update(ctx, resources); err != nil { 875 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 876 } 877 878 // Verify that the expected error is propagated to the watcher which 879 // requested for the bad resource. 880 u, err := lw1.updateCh.Receive(ctx) 881 if err != nil { 882 t.Fatalf("timeout when waiting for a listener resource from the management server: %v", err) 883 } 884 gotErr := u.(listenerUpdateErrTuple).err 885 if gotErr == nil || !strings.Contains(gotErr.Error(), wantListenerNACKErr) { 886 t.Fatalf("update received with error: %v, want %q", gotErr, wantListenerNACKErr) 887 } 888 889 // Verify that the watcher watching the good resource receives a good 890 // update. 891 wantUpdate := listenerUpdateErrTuple{ 892 update: xdsresource.ListenerUpdate{ 893 RouteConfigName: rdsName, 894 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 895 }, 896 } 897 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 898 t.Fatal(err) 899 } 900 } 901 902 // TestLDSWatch_PartialResponse covers the case where a response from the 903 // management server does not contain all requested resources. LDS responses are 904 // supposed to contain all requested resources, and the absence of one usually 905 // indicates that the management server does not know about it. In cases where 906 // the server has never responded with this resource before, the xDS client is 907 // expected to wait for the watch timeout to expire before concluding that the 908 // resource does not exist on the server 909 func (s) TestLDSWatch_PartialResponse(t *testing.T) { 910 mgmtServer, nodeID, bootstrapContents, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{}) 911 defer cleanup() 912 913 // Create an xDS client with the above bootstrap contents. 914 client, close, err := xdsclient.NewWithBootstrapContentsForTesting(bootstrapContents) 915 if err != nil { 916 t.Fatalf("Failed to create xDS client: %v", err) 917 } 918 defer close() 919 920 // Register two watches for two listener resources and have the 921 // callbacks push the received updates on to a channel. 922 resourceName1 := ldsName 923 lw1 := newListenerWatcher() 924 ldsCancel1 := xdsresource.WatchListener(client, resourceName1, lw1) 925 defer ldsCancel1() 926 927 resourceName2 := ldsNameNewStyle 928 lw2 := newListenerWatcher() 929 ldsCancel2 := xdsresource.WatchListener(client, resourceName2, lw2) 930 defer ldsCancel2() 931 932 // Configure the management server to return only one of the two listener 933 // resources, corresponding to the registered watches. 934 resources := e2e.UpdateOptions{ 935 NodeID: nodeID, 936 Listeners: []*v3listenerpb.Listener{ 937 e2e.DefaultClientListener(resourceName1, rdsName), 938 }, 939 SkipValidation: true, 940 } 941 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 942 defer cancel() 943 if err := mgmtServer.Update(ctx, resources); err != nil { 944 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 945 } 946 947 // Verify the contents of the received update for first watcher. 948 wantUpdate1 := listenerUpdateErrTuple{ 949 update: xdsresource.ListenerUpdate{ 950 RouteConfigName: rdsName, 951 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 952 }, 953 } 954 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate1); err != nil { 955 t.Fatal(err) 956 } 957 958 // Verify that the second watcher does not get an update with an error. 959 if err := verifyNoListenerUpdate(ctx, lw2.updateCh); err != nil { 960 t.Fatal(err) 961 } 962 963 // Configure the management server to return two listener resources, 964 // corresponding to the registered watches. 965 resources = e2e.UpdateOptions{ 966 NodeID: nodeID, 967 Listeners: []*v3listenerpb.Listener{ 968 e2e.DefaultClientListener(resourceName1, rdsName), 969 e2e.DefaultClientListener(resourceName2, rdsName), 970 }, 971 SkipValidation: true, 972 } 973 if err := mgmtServer.Update(ctx, resources); err != nil { 974 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 975 } 976 977 // Verify the contents of the received update for the second watcher. 978 wantUpdate2 := listenerUpdateErrTuple{ 979 update: xdsresource.ListenerUpdate{ 980 RouteConfigName: rdsName, 981 HTTPFilters: []xdsresource.HTTPFilter{{Name: "router"}}, 982 }, 983 } 984 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate2); err != nil { 985 t.Fatal(err) 986 } 987 988 // Verify that the first watcher gets no update, as the first resource did 989 // not change. 990 if err := verifyNoListenerUpdate(ctx, lw1.updateCh); err != nil { 991 t.Fatal(err) 992 } 993 }