google.golang.org/grpc@v1.74.2/xds/internal/clients/xdsclient/test/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/google/go-cmp/cmp" 29 "github.com/google/go-cmp/cmp/cmpopts" 30 "github.com/google/uuid" 31 "google.golang.org/grpc/credentials/insecure" 32 "google.golang.org/grpc/xds/internal/clients" 33 "google.golang.org/grpc/xds/internal/clients/grpctransport" 34 "google.golang.org/grpc/xds/internal/clients/internal/syncutil" 35 "google.golang.org/grpc/xds/internal/clients/internal/testutils" 36 "google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e" 37 "google.golang.org/grpc/xds/internal/clients/xdsclient" 38 xdsclientinternal "google.golang.org/grpc/xds/internal/clients/xdsclient/internal" 39 "google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource" 40 41 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 42 v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" 43 v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 44 ) 45 46 type noopListenerWatcher struct{} 47 48 func (noopListenerWatcher) ResourceChanged(_ xdsclient.ResourceData, onDone func()) { 49 onDone() 50 } 51 func (noopListenerWatcher) ResourceError(_ error, onDone func()) { 52 onDone() 53 } 54 func (noopListenerWatcher) AmbientError(_ error, onDone func()) { 55 onDone() 56 } 57 58 type listenerUpdateErrTuple struct { 59 update listenerUpdate 60 resourceErr error 61 ambientErr error 62 } 63 64 type listenerWatcher struct { 65 updateCh *testutils.Channel // Messages of type listenerUpdate 66 resourceErrCh *testutils.Channel // Messages of type resource error 67 ambientErrCh *testutils.Channel // Messages of type ambient error 68 } 69 70 func newListenerWatcher() *listenerWatcher { 71 return &listenerWatcher{ 72 updateCh: testutils.NewChannelWithSize(1), 73 resourceErrCh: testutils.NewChannelWithSize(1), 74 ambientErrCh: testutils.NewChannelWithSize(1), 75 } 76 } 77 78 func (lw *listenerWatcher) ResourceChanged(update xdsclient.ResourceData, onDone func()) { 79 lisData, ok := update.(*listenerResourceData) 80 if !ok { 81 lw.resourceErrCh.Send(listenerUpdateErrTuple{resourceErr: fmt.Errorf("unexpected resource type: %T", update)}) 82 onDone() 83 return 84 } 85 select { 86 case <-lw.updateCh.C: 87 default: 88 } 89 lw.updateCh.Send(listenerUpdateErrTuple{update: lisData.Resource}) 90 onDone() 91 } 92 93 func (lw *listenerWatcher) AmbientError(err error, onDone func()) { 94 // When used with a go-control-plane management server that continuously 95 // resends resources which are NACKed by the xDS client, using a `Replace()` 96 // here and in OnResourceDoesNotExist() simplifies tests which will have 97 // access to the most recently received error. 98 lw.ambientErrCh.Replace(listenerUpdateErrTuple{ambientErr: err}) 99 onDone() 100 } 101 102 func (lw *listenerWatcher) ResourceError(err error, onDone func()) { 103 lw.resourceErrCh.Replace(listenerUpdateErrTuple{resourceErr: err}) 104 onDone() 105 } 106 107 // badListenerResource returns a listener resource for the given name which does 108 // not contain the `RouteSpecifier` field in the HTTPConnectionManager, and 109 // hence is expected to be NACKed by the client. 110 func badListenerResource(t *testing.T, name string) *v3listenerpb.Listener { 111 hcm := testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{}) 112 return &v3listenerpb.Listener{ 113 Name: name, 114 ApiListener: &v3listenerpb.ApiListener{ApiListener: hcm}, 115 } 116 } 117 118 func overrideWatchExpiryTimeout(t *testing.T, watchExpiryTimeout time.Duration) { 119 originalWatchExpiryTimeout := xdsclientinternal.WatchExpiryTimeout 120 xdsclientinternal.WatchExpiryTimeout = watchExpiryTimeout 121 t.Cleanup(func() { xdsclientinternal.WatchExpiryTimeout = originalWatchExpiryTimeout }) 122 } 123 124 // verifyNoListenerUpdate verifies that no listener update is received on the 125 // provided update channel, and returns an error if an update is received. 126 // 127 // A very short deadline is used while waiting for the update, as this function 128 // is intended to be used when an update is not expected. 129 func verifyNoListenerUpdate(ctx context.Context, updateCh *testutils.Channel) error { 130 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 131 defer sCancel() 132 if u, err := updateCh.Receive(sCtx); err != context.DeadlineExceeded { 133 return fmt.Errorf("unexpected ListenerUpdate: %v", u) 134 } 135 return nil 136 } 137 138 // verifyListenerUpdate waits for a listenerUpdateErrTuple from the provided 139 // updateCh (typically the updateCh, resourceErrCh, or ambientErrCh of 140 // listenerWatcher) and verifies that it matches the expected wantUpdate tuple. 141 // 142 // It performs the following checks: 143 // - Waits for an item on updateCh until the context deadline. 144 // - If wantUpdate contains a resourceErr or ambientErr, it compares the 145 // xdsresource.ErrorType of the received error with the expected error 146 // type. 147 // - If wantUpdate contains an update, it compares the received update with 148 // the expected update, ignoring the Raw field. 149 // 150 // Returns an error if the context expires, or if the received tuple does not 151 // match the expected tuple according to the comparison logic. 152 func verifyListenerUpdate(ctx context.Context, updateCh *testutils.Channel, wantUpdate listenerUpdateErrTuple) error { 153 u, err := updateCh.Receive(ctx) 154 if err != nil { 155 return fmt.Errorf("timeout when waiting for a listener resource from the management server: %v", err) 156 } 157 got := u.(listenerUpdateErrTuple) 158 if wantUpdate.resourceErr != nil { 159 if gotType, wantType := xdsresource.ErrType(got.resourceErr), xdsresource.ErrType(wantUpdate.resourceErr); gotType != wantType { 160 return fmt.Errorf("received update with resource error type %v, want %v", gotType, wantType) 161 } 162 } 163 if wantUpdate.ambientErr != nil { 164 if gotType, wantType := xdsresource.ErrType(got.ambientErr), xdsresource.ErrType(wantUpdate.ambientErr); gotType != wantType { 165 return fmt.Errorf("received update with ambient error type %v, want %v", gotType, wantType) 166 } 167 } 168 cmpOpts := []cmp.Option{ 169 cmpopts.EquateEmpty(), 170 cmpopts.IgnoreFields(listenerUpdate{}, "Raw"), 171 } 172 if diff := cmp.Diff(wantUpdate.update, got.update, cmpOpts...); diff != "" { 173 return fmt.Errorf("received unexpected diff in the listener resource update: (-want, got):\n%s", diff) 174 } 175 return nil 176 } 177 178 func verifyListenerResourceError(ctx context.Context, updateCh *testutils.Channel, wantErr, wantNodeID string) error { 179 u, err := updateCh.Receive(ctx) 180 if err != nil { 181 return fmt.Errorf("timeout when waiting for a listener error from the management server: %v", err) 182 } 183 gotErr := u.(listenerUpdateErrTuple).resourceErr 184 return verifyListenerError(ctx, gotErr, wantErr, wantNodeID) 185 } 186 187 func verifyListenerError(_ context.Context, gotErr error, wantErr, wantNodeID string) error { 188 if gotErr == nil || !strings.Contains(gotErr.Error(), wantErr) { 189 return fmt.Errorf("update received with error: %v, want %q", gotErr, wantErr) 190 } 191 if !strings.Contains(gotErr.Error(), wantNodeID) { 192 return fmt.Errorf("update received with error: %v, want error with node ID: %q", gotErr, wantNodeID) 193 } 194 return nil 195 } 196 197 func verifyAmbientErrorType(ctx context.Context, updateCh *testutils.Channel, wantErrType xdsresource.ErrorType, wantNodeID string) error { 198 u, err := updateCh.Receive(ctx) 199 if err != nil { 200 return fmt.Errorf("timeout when waiting for a listener error from the management server: %v", err) 201 } 202 gotErr := u.(listenerUpdateErrTuple).ambientErr 203 return verifyErrorType(gotErr, wantErrType, wantNodeID) 204 } 205 206 func verifyResourceErrorType(ctx context.Context, updateCh *testutils.Channel, wantErrType xdsresource.ErrorType, wantNodeID string) error { 207 u, err := updateCh.Receive(ctx) 208 if err != nil { 209 return fmt.Errorf("timeout when waiting for a listener error from the management server: %v", err) 210 } 211 gotErr := u.(listenerUpdateErrTuple).resourceErr 212 return verifyErrorType(gotErr, wantErrType, wantNodeID) 213 } 214 215 func verifyErrorType(gotErr error, wantErrType xdsresource.ErrorType, wantNodeID string) error { 216 if got, want := xdsresource.ErrType(gotErr), wantErrType; got != want { 217 return fmt.Errorf("update received with error %v of type: %v, want %v", gotErr, got, want) 218 } 219 if !strings.Contains(gotErr.Error(), wantNodeID) { 220 return fmt.Errorf("update received with error: %v, want error with node ID: %q", gotErr, wantNodeID) 221 } 222 return nil 223 } 224 225 // TestLDSWatch covers the case where a single watcher exists for a single 226 // listener resource. The test verifies the following scenarios: 227 // 1. An update from the management server containing the resource being 228 // watched should result in the invocation of the watch callback. 229 // 2. An update from the management server containing a resource *not* being 230 // watched should not result in the invocation of the watch callback. 231 // 3. After the watch is cancelled, an update from the management server 232 // containing the resource that was being watched should not result in the 233 // invocation of the watch callback. 234 // 235 // The test is run for old and new style names. 236 func (s) TestLDSWatch(t *testing.T) { 237 tests := []struct { 238 desc string 239 resourceName string 240 watchedResource *v3listenerpb.Listener // The resource being watched. 241 updatedWatchedResource *v3listenerpb.Listener // The watched resource after an update. 242 notWatchedResource *v3listenerpb.Listener // A resource which is not being watched. 243 wantUpdate listenerUpdateErrTuple 244 }{ 245 { 246 desc: "old style resource", 247 resourceName: ldsName, 248 watchedResource: e2e.DefaultClientListener(ldsName, rdsName), 249 updatedWatchedResource: e2e.DefaultClientListener(ldsName, "new-rds-resource"), 250 notWatchedResource: e2e.DefaultClientListener("unsubscribed-lds-resource", rdsName), 251 wantUpdate: listenerUpdateErrTuple{ 252 update: listenerUpdate{ 253 RouteConfigName: rdsName, 254 }, 255 }, 256 }, 257 { 258 desc: "new style resource", 259 resourceName: ldsNameNewStyle, 260 watchedResource: e2e.DefaultClientListener(ldsNameNewStyle, rdsNameNewStyle), 261 updatedWatchedResource: e2e.DefaultClientListener(ldsNameNewStyle, "new-rds-resource"), 262 notWatchedResource: e2e.DefaultClientListener("unsubscribed-lds-resource", rdsNameNewStyle), 263 wantUpdate: listenerUpdateErrTuple{ 264 update: listenerUpdate{ 265 RouteConfigName: rdsNameNewStyle, 266 }, 267 }, 268 }, 269 } 270 271 for _, test := range tests { 272 t.Run(test.desc, func(t *testing.T) { 273 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 274 275 nodeID := uuid.New().String() 276 277 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 278 si := clients.ServerIdentifier{ 279 ServerURI: mgmtServer.Address, 280 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 281 } 282 283 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 284 xdsClientConfig := xdsclient.Config{ 285 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 286 Node: clients.Node{ID: nodeID}, 287 TransportBuilder: grpctransport.NewBuilder(configs), 288 ResourceTypes: resourceTypes, 289 // Xdstp resource names used in this test do not specify an 290 // authority. These will end up looking up an entry with the 291 // empty key in the authorities map. Having an entry with an 292 // empty key and empty configuration, results in these 293 // resources also using the top-level configuration. 294 Authorities: map[string]xdsclient.Authority{ 295 "": {XDSServers: []xdsclient.ServerConfig{}}, 296 }, 297 } 298 299 // Create an xDS client with the above config. 300 client, err := xdsclient.New(xdsClientConfig) 301 if err != nil { 302 t.Fatalf("Failed to create xDS client: %v", err) 303 } 304 defer client.Close() 305 306 // Register a watch for a listener resource and have the watch 307 // callback push the received update on to a channel. 308 lw := newListenerWatcher() 309 ldsCancel := client.WatchResource(xdsresource.V3ListenerURL, test.resourceName, lw) 310 311 // Configure the management server to return a single listener 312 // resource, corresponding to the one we registered a watch for. 313 resources := e2e.UpdateOptions{ 314 NodeID: nodeID, 315 Listeners: []*v3listenerpb.Listener{test.watchedResource}, 316 SkipValidation: true, 317 } 318 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 319 defer cancel() 320 if err := mgmtServer.Update(ctx, resources); err != nil { 321 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 322 } 323 324 // Verify the contents of the received update. 325 if err := verifyListenerUpdate(ctx, lw.updateCh, test.wantUpdate); err != nil { 326 t.Fatal(err) 327 } 328 329 // Configure the management server to return an additional listener 330 // resource, one that we are not interested in. 331 resources = e2e.UpdateOptions{ 332 NodeID: nodeID, 333 Listeners: []*v3listenerpb.Listener{test.watchedResource, test.notWatchedResource}, 334 SkipValidation: true, 335 } 336 if err := mgmtServer.Update(ctx, resources); err != nil { 337 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 338 } 339 if err := verifyNoListenerUpdate(ctx, lw.updateCh); err != nil { 340 t.Fatal(err) 341 } 342 343 // Cancel the watch and update the resource corresponding to the original 344 // watch. Ensure that the cancelled watch callback is not invoked. 345 ldsCancel() 346 resources = e2e.UpdateOptions{ 347 NodeID: nodeID, 348 Listeners: []*v3listenerpb.Listener{test.updatedWatchedResource, test.notWatchedResource}, 349 SkipValidation: true, 350 } 351 if err := mgmtServer.Update(ctx, resources); err != nil { 352 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 353 } 354 if err := verifyNoListenerUpdate(ctx, lw.updateCh); err != nil { 355 t.Fatal(err) 356 } 357 }) 358 } 359 } 360 361 // TestLDSWatch_TwoWatchesForSameResourceName covers the case where two watchers 362 // exist for a single listener resource. The test verifies the following 363 // scenarios: 364 // 1. An update from the management server containing the resource being 365 // watched should result in the invocation of both watch callbacks. 366 // 2. After one of the watches is cancelled, a redundant update from the 367 // management server should not result in the invocation of either of the 368 // watch callbacks. 369 // 3. An update from the management server containing the resource being 370 // watched should result in the invocation of the un-cancelled watch 371 // callback. 372 // 373 // The test is run for old and new style names. 374 func (s) TestLDSWatch_TwoWatchesForSameResourceName(t *testing.T) { 375 tests := []struct { 376 desc string 377 resourceName string 378 watchedResource *v3listenerpb.Listener // The resource being watched. 379 updatedWatchedResource *v3listenerpb.Listener // The watched resource after an update. 380 wantUpdateV1 listenerUpdateErrTuple 381 wantUpdateV2 listenerUpdateErrTuple 382 }{ 383 { 384 desc: "old style resource", 385 resourceName: ldsName, 386 watchedResource: e2e.DefaultClientListener(ldsName, rdsName), 387 updatedWatchedResource: e2e.DefaultClientListener(ldsName, "new-rds-resource"), 388 wantUpdateV1: listenerUpdateErrTuple{ 389 update: listenerUpdate{ 390 RouteConfigName: rdsName, 391 }, 392 }, 393 wantUpdateV2: listenerUpdateErrTuple{ 394 update: listenerUpdate{ 395 RouteConfigName: "new-rds-resource", 396 }, 397 }, 398 }, 399 { 400 desc: "new style resource", 401 resourceName: ldsNameNewStyle, 402 watchedResource: e2e.DefaultClientListener(ldsNameNewStyle, rdsNameNewStyle), 403 updatedWatchedResource: e2e.DefaultClientListener(ldsNameNewStyle, "new-rds-resource"), 404 wantUpdateV1: listenerUpdateErrTuple{ 405 update: listenerUpdate{ 406 RouteConfigName: rdsNameNewStyle, 407 }, 408 }, 409 wantUpdateV2: listenerUpdateErrTuple{ 410 update: listenerUpdate{ 411 RouteConfigName: "new-rds-resource", 412 }, 413 }, 414 }, 415 } 416 417 for _, test := range tests { 418 t.Run(test.desc, func(t *testing.T) { 419 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 420 421 nodeID := uuid.New().String() 422 423 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 424 si := clients.ServerIdentifier{ 425 ServerURI: mgmtServer.Address, 426 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 427 } 428 429 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 430 xdsClientConfig := xdsclient.Config{ 431 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 432 Node: clients.Node{ID: nodeID}, 433 TransportBuilder: grpctransport.NewBuilder(configs), 434 ResourceTypes: resourceTypes, 435 // Xdstp resource names used in this test do not specify an 436 // authority. These will end up looking up an entry with the 437 // empty key in the authorities map. Having an entry with an 438 // empty key and empty configuration, results in these 439 // resources also using the top-level configuration. 440 Authorities: map[string]xdsclient.Authority{ 441 "": {XDSServers: []xdsclient.ServerConfig{}}, 442 }, 443 } 444 445 // Create an xDS client with the above config. 446 client, err := xdsclient.New(xdsClientConfig) 447 if err != nil { 448 t.Fatalf("Failed to create xDS client: %v", err) 449 } 450 defer client.Close() 451 452 // Register two watches for the same listener resource and have the 453 // callbacks push the received updates on to a channel. 454 lw1 := newListenerWatcher() 455 ldsCancel1 := client.WatchResource(xdsresource.V3ListenerURL, test.resourceName, lw1) 456 defer ldsCancel1() 457 lw2 := newListenerWatcher() 458 ldsCancel2 := client.WatchResource(xdsresource.V3ListenerURL, test.resourceName, lw2) 459 460 // Configure the management server to return a single listener 461 // resource, corresponding to the one we registered watches for. 462 resources := e2e.UpdateOptions{ 463 NodeID: nodeID, 464 Listeners: []*v3listenerpb.Listener{test.watchedResource}, 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. 474 if err := verifyListenerUpdate(ctx, lw1.updateCh, test.wantUpdateV1); err != nil { 475 t.Fatal(err) 476 } 477 if err := verifyListenerUpdate(ctx, lw2.updateCh, test.wantUpdateV1); err != nil { 478 t.Fatal(err) 479 } 480 481 // Cancel the second watch and force the management server to push a 482 // redundant update for the resource being watched. Neither of the 483 // two watch callbacks should be invoked. 484 ldsCancel2() 485 if err := mgmtServer.Update(ctx, resources); err != nil { 486 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 487 } 488 if err := verifyNoListenerUpdate(ctx, lw1.updateCh); err != nil { 489 t.Fatal(err) 490 } 491 if err := verifyNoListenerUpdate(ctx, lw2.updateCh); err != nil { 492 t.Fatal(err) 493 } 494 495 // Update to the resource being watched. The un-cancelled callback 496 // should be invoked while the cancelled one should not be. 497 resources = e2e.UpdateOptions{ 498 NodeID: nodeID, 499 Listeners: []*v3listenerpb.Listener{test.updatedWatchedResource}, 500 SkipValidation: true, 501 } 502 if err := mgmtServer.Update(ctx, resources); err != nil { 503 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 504 } 505 if err := verifyListenerUpdate(ctx, lw1.updateCh, test.wantUpdateV2); err != nil { 506 t.Fatal(err) 507 } 508 if err := verifyNoListenerUpdate(ctx, lw2.updateCh); err != nil { 509 t.Fatal(err) 510 } 511 }) 512 } 513 } 514 515 // TestLDSWatch_ThreeWatchesForDifferentResourceNames covers the case with three 516 // watchers (two watchers for one resource, and the third watcher for another 517 // resource), exist across two listener resources. The test verifies that an 518 // update from the management server containing both resources results in the 519 // invocation of all watch callbacks. 520 // 521 // The test is run with both old and new style names. 522 func (s) TestLDSWatch_ThreeWatchesForDifferentResourceNames(t *testing.T) { 523 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 524 525 nodeID := uuid.New().String() 526 authority := makeAuthorityName(t.Name()) 527 528 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 529 si := clients.ServerIdentifier{ 530 ServerURI: mgmtServer.Address, 531 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 532 } 533 534 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 535 xdsClientConfig := xdsclient.Config{ 536 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 537 Node: clients.Node{ID: nodeID}, 538 TransportBuilder: grpctransport.NewBuilder(configs), 539 ResourceTypes: resourceTypes, 540 // Xdstp style resource names used in this test use a slash removed 541 // version of t.Name as their authority, and the empty config 542 // results in the top-level xds server configuration being used for 543 // this authority. 544 Authorities: map[string]xdsclient.Authority{ 545 authority: {XDSServers: []xdsclient.ServerConfig{}}, 546 }, 547 } 548 549 // Create an xDS client with the above config. 550 client, err := xdsclient.New(xdsClientConfig) 551 if err != nil { 552 t.Fatalf("Failed to create xDS client: %v", err) 553 } 554 defer client.Close() 555 556 // Register two watches for the same listener resource and have the 557 // callbacks push the received updates on to a channel. 558 lw1 := newListenerWatcher() 559 ldsCancel1 := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw1) 560 defer ldsCancel1() 561 lw2 := newListenerWatcher() 562 ldsCancel2 := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw2) 563 defer ldsCancel2() 564 565 // Register the third watch for a different listener resource. 566 ldsNameNewStyle := makeNewStyleLDSName(authority) 567 lw3 := newListenerWatcher() 568 ldsCancel3 := client.WatchResource(xdsresource.V3ListenerURL, ldsNameNewStyle, lw3) 569 defer ldsCancel3() 570 571 // Configure the management server to return two listener resources, 572 // corresponding to the registered watches. 573 resources := e2e.UpdateOptions{ 574 NodeID: nodeID, 575 Listeners: []*v3listenerpb.Listener{ 576 e2e.DefaultClientListener(ldsName, rdsName), 577 e2e.DefaultClientListener(ldsNameNewStyle, rdsName), 578 }, 579 SkipValidation: true, 580 } 581 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 582 defer cancel() 583 if err := mgmtServer.Update(ctx, resources); err != nil { 584 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 585 } 586 587 // Verify the contents of the received update for the all watchers. The two 588 // resources returned differ only in the resource name. Therefore the 589 // expected update is the same for all the watchers. 590 wantUpdate := listenerUpdateErrTuple{ 591 update: listenerUpdate{ 592 RouteConfigName: rdsName, 593 }, 594 } 595 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate); err != nil { 596 t.Fatal(err) 597 } 598 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 599 t.Fatal(err) 600 } 601 if err := verifyListenerUpdate(ctx, lw3.updateCh, wantUpdate); err != nil { 602 t.Fatal(err) 603 } 604 } 605 606 // TestLDSWatch_ResourceCaching covers the case where a watch is registered for 607 // a resource which is already present in the cache. The test verifies that the 608 // watch callback is invoked with the contents from the cache, instead of a 609 // request being sent to the management server. 610 func (s) TestLDSWatch_ResourceCaching(t *testing.T) { 611 firstRequestReceived := false 612 firstAckReceived := syncutil.NewEvent() 613 secondRequestReceived := syncutil.NewEvent() 614 615 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{ 616 OnStreamRequest: func(_ int64, req *v3discoverypb.DiscoveryRequest) error { 617 // The first request has an empty version string. 618 if !firstRequestReceived && req.GetVersionInfo() == "" { 619 firstRequestReceived = true 620 return nil 621 } 622 // The first ack has a non-empty version string. 623 if !firstAckReceived.HasFired() && req.GetVersionInfo() != "" { 624 firstAckReceived.Fire() 625 return nil 626 } 627 // Any requests after the first request and ack, are not expected. 628 secondRequestReceived.Fire() 629 return nil 630 }, 631 }) 632 633 nodeID := uuid.New().String() 634 635 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 636 si := clients.ServerIdentifier{ 637 ServerURI: mgmtServer.Address, 638 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 639 } 640 641 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 642 xdsClientConfig := xdsclient.Config{ 643 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 644 Node: clients.Node{ID: nodeID}, 645 TransportBuilder: grpctransport.NewBuilder(configs), 646 ResourceTypes: resourceTypes, 647 } 648 649 // Create an xDS client with the above config. 650 client, err := xdsclient.New(xdsClientConfig) 651 if err != nil { 652 t.Fatalf("Failed to create xDS client: %v", err) 653 } 654 defer client.Close() 655 656 // Register a watch for a listener resource and have the watch 657 // callback push the received update on to a channel. 658 lw1 := newListenerWatcher() 659 ldsCancel1 := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw1) 660 defer ldsCancel1() 661 662 // Configure the management server to return a single listener 663 // resource, corresponding to the one we registered a watch for. 664 resources := e2e.UpdateOptions{ 665 NodeID: nodeID, 666 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(ldsName, rdsName)}, 667 SkipValidation: true, 668 } 669 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 670 defer cancel() 671 if err := mgmtServer.Update(ctx, resources); err != nil { 672 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 673 } 674 675 // Verify the contents of the received update. 676 wantUpdate := listenerUpdateErrTuple{ 677 update: listenerUpdate{ 678 RouteConfigName: rdsName, 679 }, 680 } 681 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate); err != nil { 682 t.Fatal(err) 683 } 684 select { 685 case <-ctx.Done(): 686 t.Fatal("timeout when waiting for receipt of ACK at the management server") 687 case <-firstAckReceived.Done(): 688 } 689 690 // Register another watch for the same resource. This should get the update 691 // from the cache. 692 lw2 := newListenerWatcher() 693 ldsCancel2 := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw2) 694 defer ldsCancel2() 695 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 696 t.Fatal(err) 697 } 698 // No request should get sent out as part of this watch. 699 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 700 defer sCancel() 701 select { 702 case <-sCtx.Done(): 703 case <-secondRequestReceived.Done(): 704 t.Fatal("xdsClient sent out request instead of using update from cache") 705 } 706 } 707 708 // TestLDSWatch_ExpiryTimerFiresBeforeResponse tests the case where the client 709 // does not receive an LDS response for the request that it sends. The test 710 // verifies that the watch callback is invoked with an error once the 711 // watchExpiryTimer fires. 712 func (s) TestLDSWatch_ExpiryTimerFiresBeforeResponse(t *testing.T) { 713 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 714 715 nodeID := uuid.New().String() 716 717 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 718 si := clients.ServerIdentifier{ 719 ServerURI: mgmtServer.Address, 720 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 721 } 722 723 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 724 xdsClientConfig := xdsclient.Config{ 725 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 726 Node: clients.Node{ID: nodeID}, 727 TransportBuilder: grpctransport.NewBuilder(configs), 728 ResourceTypes: resourceTypes, 729 } 730 731 // Create an xDS client with the above config and override the default 732 // watch expiry timeout. 733 overrideWatchExpiryTimeout(t, defaultTestWatchExpiryTimeout) 734 client, err := xdsclient.New(xdsClientConfig) 735 if err != nil { 736 t.Fatalf("Failed to create xDS client: %v", err) 737 } 738 defer client.Close() 739 740 // Register a watch for a resource which is expected to fail with an error 741 // after the watch expiry timer fires. 742 lw := newListenerWatcher() 743 ldsCancel := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw) 744 defer ldsCancel() 745 746 // Wait for the watch expiry timer to fire. 747 <-time.After(defaultTestWatchExpiryTimeout) 748 749 // Verify that an empty update with the expected resource error is 750 // received. 751 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 752 defer cancel() 753 wantErr := xdsresource.NewError(xdsresource.ErrorTypeResourceNotFound, "") 754 if err := verifyListenerUpdate(ctx, lw.resourceErrCh, listenerUpdateErrTuple{resourceErr: wantErr}); err != nil { 755 t.Fatal(err) 756 } 757 } 758 759 // TestLDSWatch_ValidResponseCancelsExpiryTimerBehavior tests the case where the 760 // client receives a valid LDS response for the request that it sends. The test 761 // verifies that the behavior associated with the expiry timer (i.e, callback 762 // invocation with error) does not take place. 763 func (s) TestLDSWatch_ValidResponseCancelsExpiryTimerBehavior(t *testing.T) { 764 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 765 766 nodeID := uuid.New().String() 767 768 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 769 si := clients.ServerIdentifier{ 770 ServerURI: mgmtServer.Address, 771 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 772 } 773 774 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 775 xdsClientConfig := xdsclient.Config{ 776 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 777 Node: clients.Node{ID: nodeID}, 778 TransportBuilder: grpctransport.NewBuilder(configs), 779 ResourceTypes: resourceTypes, 780 } 781 782 // Create an xDS client with the above config and override the default 783 // watch expiry timeout. 784 overrideWatchExpiryTimeout(t, defaultTestWatchExpiryTimeout) 785 client, err := xdsclient.New(xdsClientConfig) 786 if err != nil { 787 t.Fatalf("Failed to create xDS client: %v", err) 788 } 789 defer client.Close() 790 791 // Register a watch for a listener resource and have the watch 792 // callback push the received update on to a channel. 793 lw := newListenerWatcher() 794 ldsCancel := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw) 795 defer ldsCancel() 796 797 // Configure the management server to return a single listener 798 // resource, corresponding to the one we registered a watch for. 799 resources := e2e.UpdateOptions{ 800 NodeID: nodeID, 801 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(ldsName, rdsName)}, 802 SkipValidation: true, 803 } 804 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 805 defer cancel() 806 if err := mgmtServer.Update(ctx, resources); err != nil { 807 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 808 } 809 810 // Verify the contents of the received update. 811 wantUpdate := listenerUpdateErrTuple{ 812 update: listenerUpdate{ 813 RouteConfigName: rdsName, 814 }, 815 } 816 if err := verifyListenerUpdate(ctx, lw.updateCh, wantUpdate); err != nil { 817 t.Fatal(err) 818 } 819 820 // Wait for the watch expiry timer to fire, and verify that the callback is 821 // not invoked. 822 <-time.After(defaultTestWatchExpiryTimeout) 823 if err := verifyNoListenerUpdate(ctx, lw.updateCh); err != nil { 824 t.Fatal(err) 825 } 826 } 827 828 // TestLDSWatch_ResourceRemoved covers the cases where a resource being watched 829 // is removed from the management server. The test verifies the following 830 // scenarios: 831 // 1. Removing a resource should trigger the watch callback with a resource 832 // removed error. It should not trigger the watch callback for an unrelated 833 // resource. 834 // 2. An update to another resource should result in the invocation of the watch 835 // callback associated with that resource. It should not result in the 836 // invocation of the watch callback associated with the deleted resource. 837 // 838 // The test is run with both old and new style names. 839 func (s) TestLDSWatch_ResourceRemoved(t *testing.T) { 840 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 841 842 nodeID := uuid.New().String() 843 authority := makeAuthorityName(t.Name()) 844 845 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 846 si := clients.ServerIdentifier{ 847 ServerURI: mgmtServer.Address, 848 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 849 } 850 851 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 852 xdsClientConfig := xdsclient.Config{ 853 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 854 Node: clients.Node{ID: nodeID}, 855 TransportBuilder: grpctransport.NewBuilder(configs), 856 ResourceTypes: resourceTypes, 857 // Xdstp style resource names used in this test use a slash removed 858 // version of t.Name as their authority, and the empty config 859 // results in the top-level xds server configuration being used for 860 // this authority. 861 Authorities: map[string]xdsclient.Authority{ 862 authority: {XDSServers: []xdsclient.ServerConfig{}}, 863 }, 864 } 865 866 // Create an xDS client with the above config. 867 client, err := xdsclient.New(xdsClientConfig) 868 if err != nil { 869 t.Fatalf("Failed to create xDS client: %v", err) 870 } 871 defer client.Close() 872 873 // Register two watches for two listener resources and have the 874 // callbacks push the received updates on to a channel. 875 resourceName1 := ldsName 876 lw1 := newListenerWatcher() 877 ldsCancel1 := client.WatchResource(xdsresource.V3ListenerURL, resourceName1, lw1) 878 defer ldsCancel1() 879 880 resourceName2 := makeNewStyleLDSName(authority) 881 lw2 := newListenerWatcher() 882 ldsCancel2 := client.WatchResource(xdsresource.V3ListenerURL, resourceName2, lw2) 883 defer ldsCancel2() 884 885 // Configure the management server to return two listener resources, 886 // corresponding to the registered watches. 887 resources := e2e.UpdateOptions{ 888 NodeID: nodeID, 889 Listeners: []*v3listenerpb.Listener{ 890 e2e.DefaultClientListener(resourceName1, rdsName), 891 e2e.DefaultClientListener(resourceName2, rdsName), 892 }, 893 SkipValidation: true, 894 } 895 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 896 defer cancel() 897 if err := mgmtServer.Update(ctx, resources); err != nil { 898 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 899 } 900 901 // Verify the contents of the received update for both watchers. The two 902 // resources returned differ only in the resource name. Therefore the 903 // expected update is the same for both watchers. 904 wantUpdate := listenerUpdateErrTuple{ 905 update: listenerUpdate{ 906 RouteConfigName: rdsName, 907 }, 908 } 909 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate); err != nil { 910 t.Fatal(err) 911 } 912 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 913 t.Fatal(err) 914 } 915 916 // Remove the first listener resource on the management server. 917 resources = e2e.UpdateOptions{ 918 NodeID: nodeID, 919 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(resourceName2, rdsName)}, 920 SkipValidation: true, 921 } 922 if err := mgmtServer.Update(ctx, resources); err != nil { 923 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 924 } 925 926 // The first watcher should receive a resource error for resource removal, 927 // while the second watcher should not see an update. 928 if err := verifyListenerUpdate(ctx, lw1.resourceErrCh, listenerUpdateErrTuple{ 929 resourceErr: xdsresource.NewError(xdsresource.ErrorTypeResourceNotFound, ""), 930 }); err != nil { 931 t.Fatal(err) 932 } 933 if err := verifyNoListenerUpdate(ctx, lw2.updateCh); err != nil { 934 t.Fatal(err) 935 } 936 937 // Update the second listener resource on the management server. The first 938 // watcher should not see an update, while the second watcher should. 939 resources = e2e.UpdateOptions{ 940 NodeID: nodeID, 941 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(resourceName2, "new-rds-resource")}, 942 SkipValidation: true, 943 } 944 if err := mgmtServer.Update(ctx, resources); err != nil { 945 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 946 } 947 if err := verifyNoListenerUpdate(ctx, lw1.updateCh); err != nil { 948 t.Fatal(err) 949 } 950 wantUpdate = listenerUpdateErrTuple{ 951 update: listenerUpdate{ 952 RouteConfigName: "new-rds-resource", 953 }, 954 } 955 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 956 t.Fatal(err) 957 } 958 } 959 960 // TestLDSWatch_NewWatcherForRemovedResource covers the case where a new 961 // watcher registers for a resource that has been removed. The test verifies 962 // the following scenarios: 963 // 1. When a resource is deleted by the management server, any active 964 // watchers of that resource should be notified with a "resource removed" 965 // error through their watch callback. 966 // 2. If a new watcher attempts to register for a resource that has already 967 // been deleted, its watch callback should be immediately invoked with a 968 // "resource removed" error. 969 func (s) TestLDSWatch_NewWatcherForRemovedResource(t *testing.T) { 970 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 971 972 nodeID := uuid.New().String() 973 974 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 975 si := clients.ServerIdentifier{ 976 ServerURI: mgmtServer.Address, 977 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 978 } 979 980 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 981 xdsClientConfig := xdsclient.Config{ 982 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 983 Node: clients.Node{ID: nodeID}, 984 TransportBuilder: grpctransport.NewBuilder(configs), 985 ResourceTypes: resourceTypes, 986 } 987 988 // Create an xDS client with the above config. 989 client, err := xdsclient.New(xdsClientConfig) 990 if err != nil { 991 t.Fatalf("Failed to create xDS client: %v", err) 992 } 993 defer client.Close() 994 995 // Register watch for the listener resource and have the 996 // callbacks push the received updates on to a channel. 997 lw1 := newListenerWatcher() 998 ldsCancel1 := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw1) 999 defer ldsCancel1() 1000 1001 // Configure the management server to return listener resource, 1002 // corresponding to the registered watch. 1003 resource := e2e.UpdateOptions{ 1004 NodeID: nodeID, 1005 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(ldsName, rdsName)}, 1006 SkipValidation: true, 1007 } 1008 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1009 defer cancel() 1010 if err := mgmtServer.Update(ctx, resource); err != nil { 1011 t.Fatalf("Failed to update management server with resource: %v, err: %v", resource, err) 1012 } 1013 1014 // Verify the contents of the received update for existing watch. 1015 wantUpdate := listenerUpdateErrTuple{ 1016 update: listenerUpdate{ 1017 RouteConfigName: rdsName, 1018 }, 1019 } 1020 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate); err != nil { 1021 t.Fatal(err) 1022 } 1023 1024 // Remove the listener resource on the management server. 1025 resource = e2e.UpdateOptions{ 1026 NodeID: nodeID, 1027 Listeners: []*v3listenerpb.Listener{}, 1028 SkipValidation: true, 1029 } 1030 if err := mgmtServer.Update(ctx, resource); err != nil { 1031 t.Fatalf("Failed to update management server with resource: %v, err: %v", resource, err) 1032 } 1033 1034 // The existing watcher should receive a resource error for resource 1035 // removal. 1036 updateError := listenerUpdateErrTuple{resourceErr: xdsresource.NewError(xdsresource.ErrorTypeResourceNotFound, "")} 1037 if err := verifyListenerUpdate(ctx, lw1.resourceErrCh, updateError); err != nil { 1038 t.Fatal(err) 1039 } 1040 1041 // New watchers attempting to register for a deleted resource should also 1042 // receive a "resource removed" error. 1043 lw2 := newListenerWatcher() 1044 ldsCancel2 := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw2) 1045 defer ldsCancel2() 1046 if err := verifyListenerUpdate(ctx, lw2.resourceErrCh, updateError); err != nil { 1047 t.Fatal(err) 1048 } 1049 } 1050 1051 // TestLDSWatch_NACKError covers the case where an update from the management 1052 // server is NACKed by the xdsclient. The test verifies that the error is 1053 // propagated to the existing watcher. After NACK, if a new watcher registers 1054 // for the resource, error is propagated to the new watcher as well. 1055 func (s) TestLDSWatch_NACKError(t *testing.T) { 1056 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 1057 1058 nodeID := uuid.New().String() 1059 1060 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 1061 si := clients.ServerIdentifier{ 1062 ServerURI: mgmtServer.Address, 1063 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 1064 } 1065 1066 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 1067 xdsClientConfig := xdsclient.Config{ 1068 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 1069 Node: clients.Node{ID: nodeID}, 1070 TransportBuilder: grpctransport.NewBuilder(configs), 1071 ResourceTypes: resourceTypes, 1072 } 1073 1074 // Create an xDS client with the above config. 1075 client, err := xdsclient.New(xdsClientConfig) 1076 if err != nil { 1077 t.Fatalf("Failed to create xDS client: %v", err) 1078 } 1079 defer client.Close() 1080 1081 // Register a watch for a listener resource and have the watch 1082 // callback push the received update on to a channel. 1083 lw := newListenerWatcher() 1084 ldsCancel := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw) 1085 defer ldsCancel() 1086 1087 // Configure the management server to return a single listener resource 1088 // which is expected to be NACKed by the client. 1089 resources := e2e.UpdateOptions{ 1090 NodeID: nodeID, 1091 Listeners: []*v3listenerpb.Listener{badListenerResource(t, ldsName)}, 1092 SkipValidation: true, 1093 } 1094 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1095 defer cancel() 1096 if err := mgmtServer.Update(ctx, resources); err != nil { 1097 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 1098 } 1099 1100 // Verify that the expected error is propagated to the existing watcher. 1101 // Since the resource is not cached, it should be received as resource 1102 // error. 1103 if err := verifyResourceErrorType(ctx, lw.resourceErrCh, xdsresource.ErrorTypeNACKed, nodeID); err != nil { 1104 t.Fatal(err) 1105 } 1106 1107 // Verify that the expected error is propagated to the new watcher as well. 1108 // Since the resource is not cached, it should be received as resource 1109 // error. 1110 lw2 := newListenerWatcher() 1111 ldsCancel2 := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw2) 1112 defer ldsCancel2() 1113 if err := verifyResourceErrorType(ctx, lw2.resourceErrCh, xdsresource.ErrorTypeNACKed, nodeID); err != nil { 1114 t.Fatal(err) 1115 } 1116 } 1117 1118 // Tests the scenario where a watch registered for a resource results in a good 1119 // update followed by a bad update. This results in the resource cache 1120 // containing both the old good update and the latest NACK error. The test 1121 // verifies that a when a new watch is registered for the same resource, the new 1122 // watcher receives the good update followed by the NACK error. 1123 func (s) TestLDSWatch_ResourceCaching_NACKError(t *testing.T) { 1124 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 1125 1126 nodeID := uuid.New().String() 1127 1128 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 1129 si := clients.ServerIdentifier{ 1130 ServerURI: mgmtServer.Address, 1131 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 1132 } 1133 1134 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 1135 xdsClientConfig := xdsclient.Config{ 1136 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 1137 Node: clients.Node{ID: nodeID}, 1138 TransportBuilder: grpctransport.NewBuilder(configs), 1139 ResourceTypes: resourceTypes, 1140 } 1141 1142 // Create an xDS client with the above config. 1143 client, err := xdsclient.New(xdsClientConfig) 1144 if err != nil { 1145 t.Fatalf("Failed to create xDS client: %v", err) 1146 } 1147 defer client.Close() 1148 1149 // Register a watch for a listener resource and have the watch 1150 // callback push the received update on to a channel. 1151 lw1 := newListenerWatcher() 1152 ldsCancel1 := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw1) 1153 defer ldsCancel1() 1154 1155 // Configure the management server to return a single listener 1156 // resource, corresponding to the one we registered a watch for. 1157 resources := e2e.UpdateOptions{ 1158 NodeID: nodeID, 1159 Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(ldsName, rdsName)}, 1160 SkipValidation: true, 1161 } 1162 ctx, cancel := context.WithTimeout(context.Background(), 1000*defaultTestTimeout) 1163 defer cancel() 1164 if err := mgmtServer.Update(ctx, resources); err != nil { 1165 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 1166 } 1167 1168 // Verify the contents of the received update. 1169 wantUpdate := listenerUpdateErrTuple{ 1170 update: listenerUpdate{ 1171 RouteConfigName: rdsName, 1172 }, 1173 } 1174 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate); err != nil { 1175 t.Fatal(err) 1176 } 1177 1178 // Configure the management server to return a single listener resource 1179 // which is expected to be NACKed by the client. 1180 resources = e2e.UpdateOptions{ 1181 NodeID: nodeID, 1182 Listeners: []*v3listenerpb.Listener{badListenerResource(t, ldsName)}, 1183 SkipValidation: true, 1184 } 1185 if err := mgmtServer.Update(ctx, resources); err != nil { 1186 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 1187 } 1188 1189 // Verify that the expected error is propagated to the existing watcher. 1190 // Since the resource is cached, it should be received as ambient error. 1191 if err := verifyAmbientErrorType(ctx, lw1.ambientErrCh, xdsresource.ErrorTypeNACKed, nodeID); err != nil { 1192 t.Fatal(err) 1193 } 1194 1195 // Register another watch for the same resource. This should get the update 1196 // and error from the cache. 1197 lw2 := newListenerWatcher() 1198 ldsCancel2 := client.WatchResource(xdsresource.V3ListenerURL, ldsName, lw2) 1199 defer ldsCancel2() 1200 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 1201 t.Fatal(err) 1202 } 1203 // Verify that the expected error is propagated to the existing watcher. 1204 // Since the resource is cached, it should be received as ambient error. 1205 if err := verifyAmbientErrorType(ctx, lw2.ambientErrCh, xdsresource.ErrorTypeNACKed, nodeID); err != nil { 1206 t.Fatal(err) 1207 } 1208 } 1209 1210 // TestLDSWatch_PartialValid covers the case where a response from the 1211 // management server contains both valid and invalid resources and is expected 1212 // to be NACKed by the xdsclient. The test verifies that watchers corresponding 1213 // to the valid resource receive the update, while watchers corresponding to the 1214 // invalid resource receive an error. 1215 func (s) TestLDSWatch_PartialValid(t *testing.T) { 1216 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 1217 1218 nodeID := uuid.New().String() 1219 authority := makeAuthorityName(t.Name()) 1220 1221 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 1222 si := clients.ServerIdentifier{ 1223 ServerURI: mgmtServer.Address, 1224 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 1225 } 1226 1227 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 1228 xdsClientConfig := xdsclient.Config{ 1229 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 1230 Node: clients.Node{ID: nodeID}, 1231 TransportBuilder: grpctransport.NewBuilder(configs), 1232 ResourceTypes: resourceTypes, 1233 // Xdstp style resource names used in this test use a slash removed 1234 // version of t.Name as their authority, and the empty config 1235 // results in the top-level xds server configuration being used for 1236 // this authority. 1237 Authorities: map[string]xdsclient.Authority{ 1238 authority: {XDSServers: []xdsclient.ServerConfig{}}, 1239 }, 1240 } 1241 1242 // Create an xDS client with the above config. 1243 client, err := xdsclient.New(xdsClientConfig) 1244 if err != nil { 1245 t.Fatalf("Failed to create xDS client: %v", err) 1246 } 1247 defer client.Close() 1248 1249 // Register two watches for listener resources. The first watch is expected 1250 // to receive an error because the received resource is NACKed. The second 1251 // watch is expected to get a good update. 1252 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1253 defer cancel() 1254 badResourceName := ldsName 1255 lw1 := newListenerWatcher() 1256 ldsCancel1 := client.WatchResource(xdsresource.V3ListenerURL, badResourceName, lw1) 1257 defer ldsCancel1() 1258 goodResourceName := makeNewStyleLDSName(authority) 1259 lw2 := newListenerWatcher() 1260 ldsCancel2 := client.WatchResource(xdsresource.V3ListenerURL, goodResourceName, lw2) 1261 defer ldsCancel2() 1262 1263 // Configure the management server with two listener resources. One of these 1264 // is a bad resource causing the update to be NACKed. 1265 resources := e2e.UpdateOptions{ 1266 NodeID: nodeID, 1267 Listeners: []*v3listenerpb.Listener{ 1268 badListenerResource(t, badResourceName), 1269 e2e.DefaultClientListener(goodResourceName, rdsName), 1270 }, 1271 SkipValidation: true, 1272 } 1273 if err := mgmtServer.Update(ctx, resources); err != nil { 1274 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 1275 } 1276 1277 // Verify that the expected error is propagated to the watcher which 1278 // requested for the bad resource. 1279 // Verify that the expected error is propagated to the existing watcher. 1280 // Since the resource is not cached, it should be received as resource 1281 // error. 1282 if err := verifyResourceErrorType(ctx, lw1.resourceErrCh, xdsresource.ErrorTypeNACKed, nodeID); err != nil { 1283 t.Fatal(err) 1284 } 1285 1286 // Verify that the watcher watching the good resource receives a good 1287 // update. 1288 wantUpdate := listenerUpdateErrTuple{ 1289 update: listenerUpdate{ 1290 RouteConfigName: rdsName, 1291 }, 1292 } 1293 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate); err != nil { 1294 t.Fatal(err) 1295 } 1296 } 1297 1298 // TestLDSWatch_PartialResponse covers the case where a response from the 1299 // management server does not contain all requested resources. LDS responses are 1300 // supposed to contain all requested resources, and the absence of one usually 1301 // indicates that the management server does not know about it. In cases where 1302 // the server has never responded with this resource before, the xDS client is 1303 // expected to wait for the watch timeout to expire before concluding that the 1304 // resource does not exist on the server 1305 func (s) TestLDSWatch_PartialResponse(t *testing.T) { 1306 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 1307 1308 nodeID := uuid.New().String() 1309 authority := makeAuthorityName(t.Name()) 1310 1311 resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType} 1312 si := clients.ServerIdentifier{ 1313 ServerURI: mgmtServer.Address, 1314 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 1315 } 1316 1317 configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}} 1318 xdsClientConfig := xdsclient.Config{ 1319 Servers: []xdsclient.ServerConfig{{ServerIdentifier: si}}, 1320 Node: clients.Node{ID: nodeID}, 1321 TransportBuilder: grpctransport.NewBuilder(configs), 1322 ResourceTypes: resourceTypes, 1323 // Xdstp style resource names used in this test use a slash removed 1324 // version of t.Name as their authority, and the empty config 1325 // results in the top-level xds server configuration being used for 1326 // this authority. 1327 Authorities: map[string]xdsclient.Authority{ 1328 authority: {XDSServers: []xdsclient.ServerConfig{}}, 1329 }, 1330 } 1331 1332 // Create an xDS client with the above config. 1333 client, err := xdsclient.New(xdsClientConfig) 1334 if err != nil { 1335 t.Fatalf("Failed to create xDS client: %v", err) 1336 } 1337 defer client.Close() 1338 1339 // Register two watches for two listener resources and have the 1340 // callbacks push the received updates on to a channel. 1341 resourceName1 := ldsName 1342 lw1 := newListenerWatcher() 1343 ldsCancel1 := client.WatchResource(xdsresource.V3ListenerURL, resourceName1, lw1) 1344 defer ldsCancel1() 1345 1346 resourceName2 := makeNewStyleLDSName(authority) 1347 lw2 := newListenerWatcher() 1348 ldsCancel2 := client.WatchResource(xdsresource.V3ListenerURL, resourceName2, lw2) 1349 defer ldsCancel2() 1350 1351 // Configure the management server to return only one of the two listener 1352 // resources, corresponding to the registered watches. 1353 resources := e2e.UpdateOptions{ 1354 NodeID: nodeID, 1355 Listeners: []*v3listenerpb.Listener{ 1356 e2e.DefaultClientListener(resourceName1, rdsName), 1357 }, 1358 SkipValidation: true, 1359 } 1360 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 1361 defer cancel() 1362 if err := mgmtServer.Update(ctx, resources); err != nil { 1363 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 1364 } 1365 1366 // Verify the contents of the received update for first watcher. 1367 wantUpdate1 := listenerUpdateErrTuple{ 1368 update: listenerUpdate{ 1369 RouteConfigName: rdsName, 1370 }, 1371 } 1372 if err := verifyListenerUpdate(ctx, lw1.updateCh, wantUpdate1); err != nil { 1373 t.Fatal(err) 1374 } 1375 1376 // Verify that the second watcher does not get an update with an error. 1377 if err := verifyNoListenerUpdate(ctx, lw2.updateCh); err != nil { 1378 t.Fatal(err) 1379 } 1380 1381 // Configure the management server to return two listener resources, 1382 // corresponding to the registered watches. 1383 resources = e2e.UpdateOptions{ 1384 NodeID: nodeID, 1385 Listeners: []*v3listenerpb.Listener{ 1386 e2e.DefaultClientListener(resourceName1, rdsName), 1387 e2e.DefaultClientListener(resourceName2, rdsName), 1388 }, 1389 SkipValidation: true, 1390 } 1391 if err := mgmtServer.Update(ctx, resources); err != nil { 1392 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 1393 } 1394 1395 // Verify the contents of the received update for the second watcher. 1396 wantUpdate2 := listenerUpdateErrTuple{ 1397 update: listenerUpdate{ 1398 RouteConfigName: rdsName, 1399 }, 1400 } 1401 if err := verifyListenerUpdate(ctx, lw2.updateCh, wantUpdate2); err != nil { 1402 t.Fatal(err) 1403 } 1404 1405 // Verify that the first watcher gets no update, as the first resource did 1406 // not change. 1407 if err := verifyNoListenerUpdate(ctx, lw1.updateCh); err != nil { 1408 t.Fatal(err) 1409 } 1410 }