diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts index b8d4a7dac..90e8756f9 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -227,13 +227,14 @@ class XdsClusterManager implements LoadBalancer { this.children.get(name)!.destroy(); this.children.delete(name); } - // Add new children that were not in the previous config + // Update all children, and add any new ones for (const [name, childConfig] of configChildren.entries()) { - if (!this.children.has(name)) { - const newChild = new this.XdsClusterManagerChildImpl(this, name); - newChild.updateAddressList(endpointList, childConfig, attributes); - this.children.set(name, newChild); + let child = this.children.get(name); + if (!child) { + child = new this.XdsClusterManagerChildImpl(this, name); + this.children.set(name, child); } + child.updateAddressList(endpointList, childConfig, attributes); } this.updatesPaused = false; this.updateState(); diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index 44154afa3..87973265b 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -386,7 +386,7 @@ export class FakeServerRoute { private listener: Listener; private routeConfiguration: RouteConfiguration; constructor(port: number, routeName: string, baseListener?: Listener | undefined, baseRouteConfiguration?: RouteConfiguration) { - this.listener = baseListener ?? DEFAULT_BASE_SERVER_LISTENER; + this.listener = baseListener ?? {...DEFAULT_BASE_SERVER_LISTENER}; this.listener.name = `[::1]:${port}`; this.listener.address = { socket_address: { @@ -414,7 +414,7 @@ export class FakeServerRoute { filterChain.filters = filterList; } - this.routeConfiguration = baseRouteConfiguration ?? DEFAULT_BASE_SERVER_ROUTE_CONFIG; + this.routeConfiguration = baseRouteConfiguration ?? {...DEFAULT_BASE_SERVER_ROUTE_CONFIG}; this.routeConfiguration.name = routeName; } diff --git a/packages/grpc-js-xds/test/test-core.ts b/packages/grpc-js-xds/test/test-core.ts index 45a96d683..267524bc6 100644 --- a/packages/grpc-js-xds/test/test-core.ts +++ b/packages/grpc-js-xds/test/test-core.ts @@ -134,5 +134,36 @@ describe('core xDS functionality', () => { }); }, reason => done(reason)); }, reason => done(reason)); + }); + it('should handle cluster config changes', async () => { + const [backend1, backend2] = await createBackends(2); + const serverRoute1 = new FakeServerRoute(backend1.getPort(), 'serverRoute'); + const serverRoute2 = new FakeServerRoute(backend2.getPort(), 'serverRoute2'); + xdsServer.setRdsResource(serverRoute1.getRouteConfiguration()); + xdsServer.setLdsResource(serverRoute1.getListener()); + xdsServer.setRdsResource(serverRoute2.getRouteConfiguration()); + xdsServer.setLdsResource(serverRoute2.getListener()); + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + client?.stopCalls(); + assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`); + } + }); + const cluster1 = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend1], locality:{region: 'region1'}}]); + const routeGroup1 = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster1}]); + await routeGroup1.startAllBackends(xdsServer); + xdsServer.setEdsResource(cluster1.getEndpointConfig()); + xdsServer.setCdsResource(cluster1.getClusterConfig()); + xdsServer.setRdsResource(routeGroup1.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup1.getListener()); + client = XdsTestClient.createFromServer('listener1', xdsServer); + client.startCalls(100); + await cluster1.waitForAllBackendsToReceiveTraffic(); + const cluster2 = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend2], locality:{region: 'region2'}}]); + await cluster2.startAllBackends(xdsServer); + xdsServer.setEdsResource(cluster2.getEndpointConfig()); + await cluster2.waitForAllBackendsToReceiveTraffic(); + client.stopCalls(); + }) });