google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/tests/eds_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 "encoding/json" 24 "fmt" 25 "strings" 26 "testing" 27 "time" 28 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/testutils" 34 "google.golang.org/grpc/internal/testutils/xds/e2e" 35 "google.golang.org/grpc/internal/xds/bootstrap" 36 "google.golang.org/grpc/xds/internal" 37 "google.golang.org/grpc/xds/internal/xdsclient" 38 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" 39 "google.golang.org/protobuf/types/known/wrapperspb" 40 41 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 42 v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 43 ) 44 45 const ( 46 edsHost1 = "1.foo.bar.com" 47 edsHost2 = "2.foo.bar.com" 48 edsHost3 = "3.foo.bar.com" 49 edsPort1 = 1 50 edsPort2 = 2 51 edsPort3 = 3 52 ) 53 54 type noopEndpointsWatcher struct{} 55 56 func (noopEndpointsWatcher) OnUpdate(update *xdsresource.EndpointsResourceData, onDone xdsresource.OnDoneFunc) { 57 onDone() 58 } 59 func (noopEndpointsWatcher) OnError(err error, onDone xdsresource.OnDoneFunc) { 60 onDone() 61 } 62 func (noopEndpointsWatcher) OnResourceDoesNotExist(onDone xdsresource.OnDoneFunc) { 63 onDone() 64 } 65 66 type endpointsUpdateErrTuple struct { 67 update xdsresource.EndpointsUpdate 68 err error 69 } 70 71 type endpointsWatcher struct { 72 updateCh *testutils.Channel 73 } 74 75 func newEndpointsWatcher() *endpointsWatcher { 76 return &endpointsWatcher{updateCh: testutils.NewChannel()} 77 } 78 79 func (ew *endpointsWatcher) OnUpdate(update *xdsresource.EndpointsResourceData, onDone xdsresource.OnDoneFunc) { 80 ew.updateCh.Send(endpointsUpdateErrTuple{update: update.Resource}) 81 onDone() 82 } 83 84 func (ew *endpointsWatcher) OnError(err error, onDone xdsresource.OnDoneFunc) { 85 // When used with a go-control-plane management server that continuously 86 // resends resources which are NACKed by the xDS client, using a `Replace()` 87 // here and in OnResourceDoesNotExist() simplifies tests which will have 88 // access to the most recently received error. 89 ew.updateCh.Replace(endpointsUpdateErrTuple{err: err}) 90 onDone() 91 } 92 93 func (ew *endpointsWatcher) OnResourceDoesNotExist(onDone xdsresource.OnDoneFunc) { 94 ew.updateCh.Replace(endpointsUpdateErrTuple{err: xdsresource.NewError(xdsresource.ErrorTypeResourceNotFound, "Endpoints not found in received response")}) 95 onDone() 96 } 97 98 // badEndpointsResource returns a endpoints resource for the given 99 // edsServiceName which contains an endpoint with a load_balancing weight of 100 // `0`. This is expected to be NACK'ed by the xDS client. 101 func badEndpointsResource(edsServiceName string, host string, ports []uint32) *v3endpointpb.ClusterLoadAssignment { 102 e := e2e.DefaultEndpoint(edsServiceName, host, ports) 103 e.Endpoints[0].LbEndpoints[0].LoadBalancingWeight = &wrapperspb.UInt32Value{Value: 0} 104 return e 105 } 106 107 // xdsClient is expected to produce an error containing this string when an 108 // update is received containing an endpoints resource created using 109 // `badEndpointsResource`. 110 const wantEndpointsNACKErr = "EDS response contains an endpoint with zero weight" 111 112 // verifyEndpointsUpdate waits for an update to be received on the provided 113 // update channel and verifies that it matches the expected update. 114 // 115 // Returns an error if no update is received before the context deadline expires 116 // or the received update does not match the expected one. 117 func verifyEndpointsUpdate(ctx context.Context, updateCh *testutils.Channel, wantUpdate endpointsUpdateErrTuple) error { 118 u, err := updateCh.Receive(ctx) 119 if err != nil { 120 return fmt.Errorf("timeout when waiting for a endpoints resource from the management server: %v", err) 121 } 122 got := u.(endpointsUpdateErrTuple) 123 if wantUpdate.err != nil { 124 if gotType, wantType := xdsresource.ErrType(got.err), xdsresource.ErrType(wantUpdate.err); gotType != wantType { 125 return fmt.Errorf("received update with error type %v, want %v", gotType, wantType) 126 } 127 } 128 cmpOpts := []cmp.Option{cmpopts.EquateEmpty(), cmpopts.IgnoreFields(xdsresource.EndpointsUpdate{}, "Raw")} 129 if diff := cmp.Diff(wantUpdate.update, got.update, cmpOpts...); diff != "" { 130 return fmt.Errorf("received unexpected diff in the endpoints resource update: (-want, got):\n%s", diff) 131 } 132 return nil 133 } 134 135 // verifyNoEndpointsUpdate verifies that no endpoints update is received on the 136 // provided update channel, and returns an error if an update is received. 137 // 138 // A very short deadline is used while waiting for the update, as this function 139 // is intended to be used when an update is not expected. 140 func verifyNoEndpointsUpdate(ctx context.Context, updateCh *testutils.Channel) error { 141 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 142 defer sCancel() 143 if u, err := updateCh.Receive(sCtx); err != context.DeadlineExceeded { 144 return fmt.Errorf("unexpected EndpointsUpdate: %v", u) 145 } 146 return nil 147 } 148 149 // TestEDSWatch covers the case where a single endpoint exists for a single 150 // endpoints resource. The test verifies the following scenarios: 151 // 1. An update from the management server containing the resource being 152 // watched should result in the invocation of the watch callback. 153 // 2. An update from the management server containing a resource *not* being 154 // watched should not result in the invocation of the watch callback. 155 // 3. After the watch is cancelled, an update from the management server 156 // containing the resource that was being watched should not result in the 157 // invocation of the watch callback. 158 // 159 // The test is run for old and new style names. 160 func (s) TestEDSWatch(t *testing.T) { 161 tests := []struct { 162 desc string 163 resourceName string 164 watchedResource *v3endpointpb.ClusterLoadAssignment // The resource being watched. 165 updatedWatchedResource *v3endpointpb.ClusterLoadAssignment // The watched resource after an update. 166 notWatchedResource *v3endpointpb.ClusterLoadAssignment // A resource which is not being watched. 167 wantUpdate endpointsUpdateErrTuple 168 }{ 169 { 170 desc: "old style resource", 171 resourceName: edsName, 172 watchedResource: e2e.DefaultEndpoint(edsName, edsHost1, []uint32{edsPort1}), 173 updatedWatchedResource: e2e.DefaultEndpoint(edsName, edsHost2, []uint32{edsPort2}), 174 notWatchedResource: e2e.DefaultEndpoint("unsubscribed-eds-resource", edsHost3, []uint32{edsPort3}), 175 wantUpdate: endpointsUpdateErrTuple{ 176 update: xdsresource.EndpointsUpdate{ 177 Localities: []xdsresource.Locality{ 178 { 179 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost1, edsPort1)}, Weight: 1}}, 180 ID: internal.LocalityID{ 181 Region: "region-1", 182 Zone: "zone-1", 183 SubZone: "subzone-1", 184 }, 185 Priority: 0, 186 Weight: 1, 187 }, 188 }, 189 }, 190 }, 191 }, 192 { 193 desc: "new style resource", 194 resourceName: edsNameNewStyle, 195 watchedResource: e2e.DefaultEndpoint(edsNameNewStyle, edsHost1, []uint32{edsPort1}), 196 updatedWatchedResource: e2e.DefaultEndpoint(edsNameNewStyle, edsHost2, []uint32{edsPort2}), 197 notWatchedResource: e2e.DefaultEndpoint("unsubscribed-eds-resource", edsHost3, []uint32{edsPort3}), 198 wantUpdate: endpointsUpdateErrTuple{ 199 update: xdsresource.EndpointsUpdate{ 200 Localities: []xdsresource.Locality{ 201 { 202 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost1, edsPort1)}, Weight: 1}}, 203 ID: internal.LocalityID{ 204 Region: "region-1", 205 Zone: "zone-1", 206 SubZone: "subzone-1", 207 }, 208 Priority: 0, 209 Weight: 1, 210 }, 211 }, 212 }, 213 }, 214 }, 215 } 216 217 for _, test := range tests { 218 t.Run(test.desc, func(t *testing.T) { 219 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 220 221 nodeID := uuid.New().String() 222 bc, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{ 223 Servers: []byte(fmt.Sprintf(`[{ 224 "server_uri": %q, 225 "channel_creds": [{"type": "insecure"}] 226 }]`, mgmtServer.Address)), 227 Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)), 228 Authorities: map[string]json.RawMessage{ 229 // Xdstp resource names used in this test do not specify an 230 // authority. These will end up looking up an entry with the 231 // empty key in the authorities map. Having an entry with an 232 // empty key and empty configuration, results in these 233 // resources also using the top-level configuration. 234 "": []byte(`{}`), 235 }, 236 }) 237 if err != nil { 238 t.Fatalf("Failed to create bootstrap configuration: %v", err) 239 } 240 241 // Create an xDS client with the above bootstrap contents. 242 config, err := bootstrap.NewConfigFromContents(bc) 243 if err != nil { 244 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 245 } 246 pool := xdsclient.NewPool(config) 247 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 248 Name: t.Name(), 249 }) 250 if err != nil { 251 t.Fatalf("Failed to create xDS client: %v", err) 252 } 253 defer close() 254 255 // Register a watch for a endpoint resource and have the watch 256 // callback push the received update on to a channel. 257 ew := newEndpointsWatcher() 258 edsCancel := xdsresource.WatchEndpoints(client, test.resourceName, ew) 259 260 // Configure the management server to return a single endpoint 261 // resource, corresponding to the one being watched. 262 resources := e2e.UpdateOptions{ 263 NodeID: nodeID, 264 Endpoints: []*v3endpointpb.ClusterLoadAssignment{test.watchedResource}, 265 SkipValidation: true, 266 } 267 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 268 defer cancel() 269 if err := mgmtServer.Update(ctx, resources); err != nil { 270 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 271 } 272 273 // Verify the contents of the received update. 274 if err := verifyEndpointsUpdate(ctx, ew.updateCh, test.wantUpdate); err != nil { 275 t.Fatal(err) 276 } 277 278 // Configure the management server to return an additional endpoint 279 // resource, one that we are not interested in. 280 resources = e2e.UpdateOptions{ 281 NodeID: nodeID, 282 Endpoints: []*v3endpointpb.ClusterLoadAssignment{test.watchedResource, test.notWatchedResource}, 283 SkipValidation: true, 284 } 285 if err := mgmtServer.Update(ctx, resources); err != nil { 286 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 287 } 288 if err := verifyNoEndpointsUpdate(ctx, ew.updateCh); err != nil { 289 t.Fatal(err) 290 } 291 292 // Cancel the watch and update the resource corresponding to the original 293 // watch. Ensure that the cancelled watch callback is not invoked. 294 edsCancel() 295 resources = e2e.UpdateOptions{ 296 NodeID: nodeID, 297 Endpoints: []*v3endpointpb.ClusterLoadAssignment{test.updatedWatchedResource, test.notWatchedResource}, 298 SkipValidation: true, 299 } 300 if err := mgmtServer.Update(ctx, resources); err != nil { 301 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 302 } 303 if err := verifyNoEndpointsUpdate(ctx, ew.updateCh); err != nil { 304 t.Fatal(err) 305 } 306 }) 307 } 308 } 309 310 // TestEDSWatch_TwoWatchesForSameResourceName covers the case where two watchers 311 // exist for a single endpoint resource. The test verifies the following 312 // scenarios: 313 // 1. An update from the management server containing the resource being 314 // watched should result in the invocation of both watch callbacks. 315 // 2. After one of the watches is cancelled, a redundant update from the 316 // management server should not result in the invocation of either of the 317 // watch callbacks. 318 // 3. An update from the management server containing the resource being 319 // watched should result in the invocation of the un-cancelled watch 320 // callback. 321 // 322 // The test is run for old and new style names. 323 func (s) TestEDSWatch_TwoWatchesForSameResourceName(t *testing.T) { 324 tests := []struct { 325 desc string 326 resourceName string 327 watchedResource *v3endpointpb.ClusterLoadAssignment // The resource being watched. 328 updatedWatchedResource *v3endpointpb.ClusterLoadAssignment // The watched resource after an update. 329 wantUpdateV1 endpointsUpdateErrTuple 330 wantUpdateV2 endpointsUpdateErrTuple 331 }{ 332 { 333 desc: "old style resource", 334 resourceName: edsName, 335 watchedResource: e2e.DefaultEndpoint(edsName, edsHost1, []uint32{edsPort1}), 336 updatedWatchedResource: e2e.DefaultEndpoint(edsName, edsHost2, []uint32{edsPort2}), 337 wantUpdateV1: endpointsUpdateErrTuple{ 338 update: xdsresource.EndpointsUpdate{ 339 Localities: []xdsresource.Locality{ 340 { 341 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost1, edsPort1)}, Weight: 1}}, 342 ID: internal.LocalityID{ 343 Region: "region-1", 344 Zone: "zone-1", 345 SubZone: "subzone-1", 346 }, 347 Priority: 0, 348 Weight: 1, 349 }, 350 }, 351 }, 352 }, 353 wantUpdateV2: endpointsUpdateErrTuple{ 354 update: xdsresource.EndpointsUpdate{ 355 Localities: []xdsresource.Locality{ 356 { 357 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost2, edsPort2)}, Weight: 1}}, 358 ID: internal.LocalityID{ 359 Region: "region-1", 360 Zone: "zone-1", 361 SubZone: "subzone-1", 362 }, 363 Priority: 0, 364 Weight: 1, 365 }, 366 }, 367 }, 368 }, 369 }, 370 { 371 desc: "new style resource", 372 resourceName: edsNameNewStyle, 373 watchedResource: e2e.DefaultEndpoint(edsNameNewStyle, edsHost1, []uint32{edsPort1}), 374 updatedWatchedResource: e2e.DefaultEndpoint(edsNameNewStyle, edsHost2, []uint32{edsPort2}), 375 wantUpdateV1: endpointsUpdateErrTuple{ 376 update: xdsresource.EndpointsUpdate{ 377 Localities: []xdsresource.Locality{ 378 { 379 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost1, edsPort1)}, Weight: 1}}, 380 ID: internal.LocalityID{ 381 Region: "region-1", 382 Zone: "zone-1", 383 SubZone: "subzone-1", 384 }, 385 Priority: 0, 386 Weight: 1, 387 }, 388 }, 389 }, 390 }, 391 wantUpdateV2: endpointsUpdateErrTuple{ 392 update: xdsresource.EndpointsUpdate{ 393 Localities: []xdsresource.Locality{ 394 { 395 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost2, edsPort2)}, Weight: 1}}, 396 ID: internal.LocalityID{ 397 Region: "region-1", 398 Zone: "zone-1", 399 SubZone: "subzone-1", 400 }, 401 Priority: 0, 402 Weight: 1, 403 }, 404 }, 405 }, 406 }, 407 }, 408 } 409 410 for _, test := range tests { 411 t.Run(test.desc, func(t *testing.T) { 412 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 413 414 nodeID := uuid.New().String() 415 bc, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{ 416 Servers: []byte(fmt.Sprintf(`[{ 417 "server_uri": %q, 418 "channel_creds": [{"type": "insecure"}] 419 }]`, mgmtServer.Address)), 420 Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)), 421 Authorities: map[string]json.RawMessage{ 422 // Xdstp resource names used in this test do not specify an 423 // authority. These will end up looking up an entry with the 424 // empty key in the authorities map. Having an entry with an 425 // empty key and empty configuration, results in these 426 // resources also using the top-level configuration. 427 "": []byte(`{}`), 428 }, 429 }) 430 if err != nil { 431 t.Fatalf("Failed to create bootstrap configuration: %v", err) 432 } 433 434 // Create an xDS client with the above bootstrap contents. 435 config, err := bootstrap.NewConfigFromContents(bc) 436 if err != nil { 437 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 438 } 439 pool := xdsclient.NewPool(config) 440 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 441 Name: t.Name(), 442 }) 443 if err != nil { 444 t.Fatalf("Failed to create xDS client: %v", err) 445 } 446 defer close() 447 448 // Register two watches for the same endpoint resource and have the 449 // callbacks push the received updates on to a channel. 450 ew1 := newEndpointsWatcher() 451 edsCancel1 := xdsresource.WatchEndpoints(client, test.resourceName, ew1) 452 defer edsCancel1() 453 ew2 := newEndpointsWatcher() 454 edsCancel2 := xdsresource.WatchEndpoints(client, test.resourceName, ew2) 455 456 // Configure the management server to return a single endpoint 457 // resource, corresponding to the one being watched. 458 resources := e2e.UpdateOptions{ 459 NodeID: nodeID, 460 Endpoints: []*v3endpointpb.ClusterLoadAssignment{test.watchedResource}, 461 SkipValidation: true, 462 } 463 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 464 defer cancel() 465 if err := mgmtServer.Update(ctx, resources); err != nil { 466 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 467 } 468 469 // Verify the contents of the received update. 470 if err := verifyEndpointsUpdate(ctx, ew1.updateCh, test.wantUpdateV1); err != nil { 471 t.Fatal(err) 472 } 473 if err := verifyEndpointsUpdate(ctx, ew2.updateCh, test.wantUpdateV1); err != nil { 474 t.Fatal(err) 475 } 476 477 // Cancel the second watch and force the management server to push a 478 // redundant update for the resource being watched. Neither of the 479 // two watch callbacks should be invoked. 480 edsCancel2() 481 if err := mgmtServer.Update(ctx, resources); err != nil { 482 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 483 } 484 if err := verifyNoEndpointsUpdate(ctx, ew1.updateCh); err != nil { 485 t.Fatal(err) 486 } 487 if err := verifyNoEndpointsUpdate(ctx, ew2.updateCh); err != nil { 488 t.Fatal(err) 489 } 490 491 // Update to the resource being watched. The un-cancelled callback 492 // should be invoked while the cancelled one should not be. 493 resources = e2e.UpdateOptions{ 494 NodeID: nodeID, 495 Endpoints: []*v3endpointpb.ClusterLoadAssignment{test.updatedWatchedResource}, 496 SkipValidation: true, 497 } 498 if err := mgmtServer.Update(ctx, resources); err != nil { 499 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 500 } 501 if err := verifyEndpointsUpdate(ctx, ew1.updateCh, test.wantUpdateV2); err != nil { 502 t.Fatal(err) 503 } 504 if err := verifyNoEndpointsUpdate(ctx, ew2.updateCh); err != nil { 505 t.Fatal(err) 506 } 507 }) 508 } 509 } 510 511 // TestEDSWatch_ThreeWatchesForDifferentResourceNames covers the case with three 512 // watchers (two watchers for one resource, and the third watcher for another 513 // resource), exist across two endpoint configuration resources. The test verifies 514 // that an update from the management server containing both resources results 515 // in the invocation of all watch callbacks. 516 // 517 // The test is run with both old and new style names. 518 func (s) TestEDSWatch_ThreeWatchesForDifferentResourceNames(t *testing.T) { 519 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 520 521 nodeID := uuid.New().String() 522 authority := makeAuthorityName(t.Name()) 523 bc, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{ 524 Servers: []byte(fmt.Sprintf(`[{ 525 "server_uri": %q, 526 "channel_creds": [{"type": "insecure"}] 527 }]`, mgmtServer.Address)), 528 Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)), 529 Authorities: map[string]json.RawMessage{ 530 // Xdstp style resource names used in this test use a slash removed 531 // version of t.Name as their authority, and the empty config 532 // results in the top-level xds server configuration being used for 533 // this authority. 534 authority: []byte(`{}`), 535 }, 536 }) 537 if err != nil { 538 t.Fatalf("Failed to create bootstrap configuration: %v", err) 539 } 540 541 // Create an xDS client with the above bootstrap contents. 542 config, err := bootstrap.NewConfigFromContents(bc) 543 if err != nil { 544 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 545 } 546 pool := xdsclient.NewPool(config) 547 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 548 Name: t.Name(), 549 }) 550 if err != nil { 551 t.Fatalf("Failed to create xDS client: %v", err) 552 } 553 defer close() 554 555 // Register two watches for the same endpoint resource and have the 556 // callbacks push the received updates on to a channel. 557 ew1 := newEndpointsWatcher() 558 edsCancel1 := xdsresource.WatchEndpoints(client, edsName, ew1) 559 defer edsCancel1() 560 ew2 := newEndpointsWatcher() 561 edsCancel2 := xdsresource.WatchEndpoints(client, edsName, ew2) 562 defer edsCancel2() 563 564 // Register the third watch for a different endpoint resource. 565 edsNameNewStyle := makeNewStyleEDSName(authority) 566 ew3 := newEndpointsWatcher() 567 edsCancel3 := xdsresource.WatchEndpoints(client, edsNameNewStyle, ew3) 568 defer edsCancel3() 569 570 // Configure the management server to return two endpoint resources, 571 // corresponding to the registered watches. 572 resources := e2e.UpdateOptions{ 573 NodeID: nodeID, 574 Endpoints: []*v3endpointpb.ClusterLoadAssignment{ 575 e2e.DefaultEndpoint(edsName, edsHost1, []uint32{edsPort1}), 576 e2e.DefaultEndpoint(edsNameNewStyle, edsHost1, []uint32{edsPort1}), 577 }, 578 SkipValidation: true, 579 } 580 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 581 defer cancel() 582 if err := mgmtServer.Update(ctx, resources); err != nil { 583 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 584 } 585 586 // Verify the contents of the received update for the all watchers. The two 587 // resources returned differ only in the resource name. Therefore the 588 // expected update is the same for all the watchers. 589 wantUpdate := endpointsUpdateErrTuple{ 590 update: xdsresource.EndpointsUpdate{ 591 Localities: []xdsresource.Locality{ 592 { 593 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost1, edsPort1)}, Weight: 1}}, 594 ID: internal.LocalityID{ 595 Region: "region-1", 596 Zone: "zone-1", 597 SubZone: "subzone-1", 598 }, 599 Priority: 0, 600 Weight: 1, 601 }, 602 }, 603 }, 604 } 605 if err := verifyEndpointsUpdate(ctx, ew1.updateCh, wantUpdate); err != nil { 606 t.Fatal(err) 607 } 608 if err := verifyEndpointsUpdate(ctx, ew2.updateCh, wantUpdate); err != nil { 609 t.Fatal(err) 610 } 611 if err := verifyEndpointsUpdate(ctx, ew3.updateCh, wantUpdate); err != nil { 612 t.Fatal(err) 613 } 614 } 615 616 // TestEDSWatch_ResourceCaching covers the case where a watch is registered for 617 // a resource which is already present in the cache. The test verifies that the 618 // watch callback is invoked with the contents from the cache, instead of a 619 // request being sent to the management server. 620 func (s) TestEDSWatch_ResourceCaching(t *testing.T) { 621 firstRequestReceived := false 622 firstAckReceived := grpcsync.NewEvent() 623 secondRequestReceived := grpcsync.NewEvent() 624 625 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{ 626 OnStreamRequest: func(id int64, req *v3discoverypb.DiscoveryRequest) error { 627 // The first request has an empty version string. 628 if !firstRequestReceived && req.GetVersionInfo() == "" { 629 firstRequestReceived = true 630 return nil 631 } 632 // The first ack has a non-empty version string. 633 if !firstAckReceived.HasFired() && req.GetVersionInfo() != "" { 634 firstAckReceived.Fire() 635 return nil 636 } 637 // Any requests after the first request and ack, are not expected. 638 secondRequestReceived.Fire() 639 return nil 640 }, 641 }) 642 643 nodeID := uuid.New().String() 644 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 645 646 // Create an xDS client with the above bootstrap contents. 647 config, err := bootstrap.NewConfigFromContents(bc) 648 if err != nil { 649 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 650 } 651 pool := xdsclient.NewPool(config) 652 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 653 Name: t.Name(), 654 }) 655 if err != nil { 656 t.Fatalf("Failed to create xDS client: %v", err) 657 } 658 defer close() 659 660 // Register a watch for an endpoint resource and have the watch callback 661 // push the received update on to a channel. 662 ew1 := newEndpointsWatcher() 663 edsCancel1 := xdsresource.WatchEndpoints(client, edsName, ew1) 664 defer edsCancel1() 665 666 // Configure the management server to return a single endpoint resource, 667 // corresponding to the one we registered a watch for. 668 resources := e2e.UpdateOptions{ 669 NodeID: nodeID, 670 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(edsName, edsHost1, []uint32{edsPort1})}, 671 SkipValidation: true, 672 } 673 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 674 defer cancel() 675 if err := mgmtServer.Update(ctx, resources); err != nil { 676 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 677 } 678 679 // Verify the contents of the received update. 680 wantUpdate := endpointsUpdateErrTuple{ 681 update: xdsresource.EndpointsUpdate{ 682 Localities: []xdsresource.Locality{ 683 { 684 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost1, edsPort1)}, Weight: 1}}, 685 ID: internal.LocalityID{ 686 Region: "region-1", 687 Zone: "zone-1", 688 SubZone: "subzone-1", 689 }, 690 Priority: 0, 691 Weight: 1, 692 }, 693 }, 694 }, 695 } 696 if err := verifyEndpointsUpdate(ctx, ew1.updateCh, wantUpdate); err != nil { 697 t.Fatal(err) 698 } 699 select { 700 case <-ctx.Done(): 701 t.Fatal("timeout when waiting for receipt of ACK at the management server") 702 case <-firstAckReceived.Done(): 703 } 704 705 // Register another watch for the same resource. This should get the update 706 // from the cache. 707 ew2 := newEndpointsWatcher() 708 edsCancel2 := xdsresource.WatchEndpoints(client, edsName, ew2) 709 defer edsCancel2() 710 if err := verifyEndpointsUpdate(ctx, ew2.updateCh, wantUpdate); err != nil { 711 t.Fatal(err) 712 } 713 714 // No request should get sent out as part of this watch. 715 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 716 defer sCancel() 717 select { 718 case <-sCtx.Done(): 719 case <-secondRequestReceived.Done(): 720 t.Fatal("xdsClient sent out request instead of using update from cache") 721 } 722 } 723 724 // TestEDSWatch_ExpiryTimerFiresBeforeResponse tests the case where the client 725 // does not receive an EDS response for the request that it sends. The test 726 // verifies that the watch callback is invoked with an error once the 727 // watchExpiryTimer fires. 728 func (s) TestEDSWatch_ExpiryTimerFiresBeforeResponse(t *testing.T) { 729 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 730 731 nodeID := uuid.New().String() 732 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 733 734 config, err := bootstrap.NewConfigFromContents(bc) 735 if err != nil { 736 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 737 } 738 pool := xdsclient.NewPool(config) 739 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 740 Name: t.Name(), 741 WatchExpiryTimeout: defaultTestWatchExpiryTimeout, 742 }) 743 if err != nil { 744 t.Fatalf("Failed to create an xDS client: %v", err) 745 } 746 defer close() 747 748 // Register a watch for a resource which is expected to fail with an error 749 // after the watch expiry timer fires. 750 ew := newEndpointsWatcher() 751 edsCancel := xdsresource.WatchEndpoints(client, edsName, ew) 752 defer edsCancel() 753 754 // Wait for the watch expiry timer to fire. 755 <-time.After(defaultTestWatchExpiryTimeout) 756 757 // Verify that an empty update with the expected error is received. 758 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 759 defer cancel() 760 wantErr := xdsresource.NewError(xdsresource.ErrorTypeResourceNotFound, "") 761 if err := verifyEndpointsUpdate(ctx, ew.updateCh, endpointsUpdateErrTuple{err: wantErr}); err != nil { 762 t.Fatal(err) 763 } 764 } 765 766 // TestEDSWatch_ValidResponseCancelsExpiryTimerBehavior tests the case where the 767 // client receives a valid EDS response for the request that it sends. The test 768 // verifies that the behavior associated with the expiry timer (i.e, callback 769 // invocation with error) does not take place. 770 func (s) TestEDSWatch_ValidResponseCancelsExpiryTimerBehavior(t *testing.T) { 771 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 772 773 // Create an xDS client talking to the above management server. 774 nodeID := uuid.New().String() 775 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 776 777 // Create an xDS client talking to the above management server. 778 config, err := bootstrap.NewConfigFromContents(bc) 779 if err != nil { 780 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 781 } 782 pool := xdsclient.NewPool(config) 783 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 784 Name: t.Name(), 785 WatchExpiryTimeout: defaultTestWatchExpiryTimeout, 786 }) 787 if err != nil { 788 t.Fatalf("Failed to create an xDS client: %v", err) 789 } 790 defer close() 791 792 // Register a watch for an endpoint resource and have the watch callback 793 // push the received update on to a channel. 794 ew := newEndpointsWatcher() 795 edsCancel := xdsresource.WatchEndpoints(client, edsName, ew) 796 defer edsCancel() 797 798 // Configure the management server to return a single endpoint resource, 799 // corresponding to the one we registered a watch for. 800 resources := e2e.UpdateOptions{ 801 NodeID: nodeID, 802 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(edsName, edsHost1, []uint32{edsPort1})}, 803 SkipValidation: true, 804 } 805 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 806 defer cancel() 807 if err := mgmtServer.Update(ctx, resources); err != nil { 808 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 809 } 810 811 // Verify the contents of the received update. 812 wantUpdate := endpointsUpdateErrTuple{ 813 update: xdsresource.EndpointsUpdate{ 814 Localities: []xdsresource.Locality{ 815 { 816 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost1, edsPort1)}, Weight: 1}}, 817 ID: internal.LocalityID{ 818 Region: "region-1", 819 Zone: "zone-1", 820 SubZone: "subzone-1", 821 }, 822 Priority: 0, 823 Weight: 1, 824 }, 825 }, 826 }, 827 } 828 if err := verifyEndpointsUpdate(ctx, ew.updateCh, wantUpdate); err != nil { 829 t.Fatal(err) 830 } 831 832 // Wait for the watch expiry timer to fire, and verify that the callback is 833 // not invoked. 834 <-time.After(defaultTestWatchExpiryTimeout) 835 if err := verifyNoEndpointsUpdate(ctx, ew.updateCh); err != nil { 836 t.Fatal(err) 837 } 838 } 839 840 // TestEDSWatch_NACKError covers the case where an update from the management 841 // server is NACK'ed by the xdsclient. The test verifies that the error is 842 // propagated to the watcher. 843 func (s) TestEDSWatch_NACKError(t *testing.T) { 844 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 845 846 nodeID := uuid.New().String() 847 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 848 849 // Create an xDS client with the above bootstrap contents. 850 config, err := bootstrap.NewConfigFromContents(bc) 851 if err != nil { 852 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 853 } 854 pool := xdsclient.NewPool(config) 855 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 856 Name: t.Name(), 857 }) 858 if err != nil { 859 t.Fatalf("Failed to create xDS client: %v", err) 860 } 861 defer close() 862 863 // Register a watch for a route configuration resource and have the watch 864 // callback push the received update on to a channel. 865 ew := newEndpointsWatcher() 866 edsCancel := xdsresource.WatchEndpoints(client, edsName, ew) 867 defer edsCancel() 868 869 // Configure the management server to return a single route configuration 870 // resource which is expected to be NACKed by the client. 871 resources := e2e.UpdateOptions{ 872 NodeID: nodeID, 873 Endpoints: []*v3endpointpb.ClusterLoadAssignment{badEndpointsResource(edsName, edsHost1, []uint32{edsPort1})}, 874 SkipValidation: true, 875 } 876 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 877 defer cancel() 878 if err := mgmtServer.Update(ctx, resources); err != nil { 879 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 880 } 881 882 // Verify that the expected error is propagated to the watcher. 883 u, err := ew.updateCh.Receive(ctx) 884 if err != nil { 885 t.Fatalf("timeout when waiting for an endpoints resource from the management server: %v", err) 886 } 887 gotErr := u.(endpointsUpdateErrTuple).err 888 if gotErr == nil || !strings.Contains(gotErr.Error(), wantEndpointsNACKErr) { 889 t.Fatalf("update received with error: %v, want %q", gotErr, wantEndpointsNACKErr) 890 } 891 } 892 893 // TestEDSWatch_PartialValid covers the case where a response from the 894 // management server contains both valid and invalid resources and is expected 895 // to be NACK'ed by the xdsclient. The test verifies that watchers corresponding 896 // to the valid resource receive the update, while watchers corresponding to the 897 // invalid resource receive an error. 898 func (s) TestEDSWatch_PartialValid(t *testing.T) { 899 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 900 901 nodeID := uuid.New().String() 902 authority := makeAuthorityName(t.Name()) 903 bc, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{ 904 Servers: []byte(fmt.Sprintf(`[{ 905 "server_uri": %q, 906 "channel_creds": [{"type": "insecure"}] 907 }]`, mgmtServer.Address)), 908 Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)), 909 Authorities: map[string]json.RawMessage{ 910 // Xdstp style resource names used in this test use a slash removed 911 // version of t.Name as their authority, and the empty config 912 // results in the top-level xds server configuration being used for 913 // this authority. 914 authority: []byte(`{}`), 915 }, 916 }) 917 if err != nil { 918 t.Fatalf("Failed to create bootstrap configuration: %v", err) 919 } 920 921 // Create an xDS client with the above bootstrap contents. 922 config, err := bootstrap.NewConfigFromContents(bc) 923 if err != nil { 924 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 925 } 926 pool := xdsclient.NewPool(config) 927 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 928 Name: t.Name(), 929 }) 930 if err != nil { 931 t.Fatalf("Failed to create xDS client: %v", err) 932 } 933 defer close() 934 935 // Register two watches for two endpoint resources. The first watch is 936 // expected to receive an error because the received resource is NACKed. 937 // The second watch is expected to get a good update. 938 badResourceName := edsName 939 ew1 := newEndpointsWatcher() 940 edsCancel1 := xdsresource.WatchEndpoints(client, badResourceName, ew1) 941 defer edsCancel1() 942 goodResourceName := makeNewStyleEDSName(authority) 943 ew2 := newEndpointsWatcher() 944 edsCancel2 := xdsresource.WatchEndpoints(client, goodResourceName, ew2) 945 defer edsCancel2() 946 947 // Configure the management server to return two endpoints resources, 948 // corresponding to the registered watches. 949 resources := e2e.UpdateOptions{ 950 NodeID: nodeID, 951 Endpoints: []*v3endpointpb.ClusterLoadAssignment{ 952 badEndpointsResource(badResourceName, edsHost1, []uint32{edsPort1}), 953 e2e.DefaultEndpoint(goodResourceName, edsHost1, []uint32{edsPort1}), 954 }, 955 SkipValidation: true, 956 } 957 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 958 defer cancel() 959 if err := mgmtServer.Update(ctx, resources); err != nil { 960 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 961 } 962 963 // Verify that the expected error is propagated to the watcher which 964 // requested for the bad resource. 965 u, err := ew1.updateCh.Receive(ctx) 966 if err != nil { 967 t.Fatalf("timeout when waiting for an endpoints resource from the management server: %v", err) 968 } 969 gotErr := u.(endpointsUpdateErrTuple).err 970 if gotErr == nil || !strings.Contains(gotErr.Error(), wantEndpointsNACKErr) { 971 t.Fatalf("update received with error: %v, want %q", gotErr, wantEndpointsNACKErr) 972 } 973 974 // Verify that the watcher watching the good resource receives an update. 975 wantUpdate := endpointsUpdateErrTuple{ 976 update: xdsresource.EndpointsUpdate{ 977 Localities: []xdsresource.Locality{ 978 { 979 Endpoints: []xdsresource.Endpoint{{Addresses: []string{fmt.Sprintf("%s:%d", edsHost1, edsPort1)}, Weight: 1}}, 980 ID: internal.LocalityID{ 981 Region: "region-1", 982 Zone: "zone-1", 983 SubZone: "subzone-1", 984 }, 985 Priority: 0, 986 Weight: 1, 987 }, 988 }, 989 }, 990 } 991 if err := verifyEndpointsUpdate(ctx, ew2.updateCh, wantUpdate); err != nil { 992 t.Fatal(err) 993 } 994 }