Skip to content

Commit e32bbc7

Browse files
committed
grpc-js-xds: Allow tests to set bootstrap info in channel args
1 parent 6bc6b86 commit e32bbc7

File tree

7 files changed

+53
-21
lines changed

7 files changed

+53
-21
lines changed

packages/grpc-js-xds/src/load-balancer-cds.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export class CdsLoadBalancer implements LoadBalancer {
125125

126126
private latestConfig: CdsLoadBalancingConfig | null = null;
127127
private latestAttributes: { [key: string]: unknown } = {};
128+
private xdsClient: XdsClient | null = null;
128129

129130
constructor(private readonly channelControlHelper: ChannelControlHelper) {
130131
this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper);
@@ -188,6 +189,7 @@ export class CdsLoadBalancer implements LoadBalancer {
188189
}
189190
trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2));
190191
this.latestAttributes = attributes;
192+
this.xdsClient = attributes.xdsClient as XdsClient;
191193

192194
/* If the cluster is changing, disable the old watcher before adding the new
193195
* one */
@@ -196,7 +198,7 @@ export class CdsLoadBalancer implements LoadBalancer {
196198
this.latestConfig?.getCluster() !== lbConfig.getCluster()
197199
) {
198200
trace('Removing old cluster watcher for cluster name ' + this.latestConfig!.getCluster());
199-
getSingletonXdsClient().removeClusterWatcher(
201+
this.xdsClient.removeClusterWatcher(
200202
this.latestConfig!.getCluster(),
201203
this.watcher
202204
);
@@ -212,7 +214,7 @@ export class CdsLoadBalancer implements LoadBalancer {
212214

213215
if (!this.isWatcherActive) {
214216
trace('Adding new cluster watcher for cluster name ' + lbConfig.getCluster());
215-
getSingletonXdsClient().addClusterWatcher(lbConfig.getCluster(), this.watcher);
217+
this.xdsClient.addClusterWatcher(lbConfig.getCluster(), this.watcher);
216218
this.isWatcherActive = true;
217219
}
218220
}
@@ -226,7 +228,7 @@ export class CdsLoadBalancer implements LoadBalancer {
226228
trace('Destroying load balancer with cluster name ' + this.latestConfig?.getCluster());
227229
this.childBalancer.destroy();
228230
if (this.isWatcherActive) {
229-
getSingletonXdsClient().removeClusterWatcher(
231+
this.xdsClient?.removeClusterWatcher(
230232
this.latestConfig!.getCluster(),
231233
this.watcher
232234
);

packages/grpc-js-xds/src/load-balancer-eds.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export class EdsLoadBalancer implements LoadBalancer {
167167

168168
private lastestConfig: EdsLoadBalancingConfig | null = null;
169169
private latestAttributes: { [key: string]: unknown } = {};
170+
private xdsClient: XdsClient | null = null;
170171
private latestEdsUpdate: ClusterLoadAssignment__Output | null = null;
171172

172173
/**
@@ -488,13 +489,14 @@ export class EdsLoadBalancer implements LoadBalancer {
488489
trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2));
489490
this.lastestConfig = lbConfig;
490491
this.latestAttributes = attributes;
492+
this.xdsClient = attributes.xdsClient as XdsClient;
491493
const newEdsServiceName = lbConfig.getEdsServiceName() ?? lbConfig.getCluster();
492494

493495
/* If the name is changing, disable the old watcher before adding the new
494496
* one */
495497
if (this.isWatcherActive && this.edsServiceName !== newEdsServiceName) {
496498
trace('Removing old endpoint watcher for edsServiceName ' + this.edsServiceName)
497-
getSingletonXdsClient().removeEndpointWatcher(this.edsServiceName!, this.watcher);
499+
this.xdsClient.removeEndpointWatcher(this.edsServiceName!, this.watcher);
498500
/* Setting isWatcherActive to false here lets us have one code path for
499501
* calling addEndpointWatcher */
500502
this.isWatcherActive = false;
@@ -507,12 +509,12 @@ export class EdsLoadBalancer implements LoadBalancer {
507509

508510
if (!this.isWatcherActive) {
509511
trace('Adding new endpoint watcher for edsServiceName ' + this.edsServiceName);
510-
getSingletonXdsClient().addEndpointWatcher(this.edsServiceName, this.watcher);
512+
this.xdsClient.addEndpointWatcher(this.edsServiceName, this.watcher);
511513
this.isWatcherActive = true;
512514
}
513515

514516
if (lbConfig.getLrsLoadReportingServerName()) {
515-
this.clusterDropStats = getSingletonXdsClient().addClusterDropStats(
517+
this.clusterDropStats = this.xdsClient.addClusterDropStats(
516518
lbConfig.getLrsLoadReportingServerName()!,
517519
lbConfig.getCluster(),
518520
lbConfig.getEdsServiceName() ?? ''
@@ -533,7 +535,7 @@ export class EdsLoadBalancer implements LoadBalancer {
533535
destroy(): void {
534536
trace('Destroying load balancer with edsServiceName ' + this.edsServiceName);
535537
if (this.edsServiceName) {
536-
getSingletonXdsClient().removeEndpointWatcher(this.edsServiceName, this.watcher);
538+
this.xdsClient?.removeEndpointWatcher(this.edsServiceName, this.watcher);
537539
}
538540
this.childBalancer.destroy();
539541
}

packages/grpc-js-xds/src/load-balancer-lrs.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export class LrsLoadBalancer implements LoadBalancer {
169169
if (!(lbConfig instanceof LrsLoadBalancingConfig)) {
170170
return;
171171
}
172-
this.localityStatsReporter = getSingletonXdsClient().addClusterLocalityStats(
172+
this.localityStatsReporter = (attributes.xdsClient as XdsClient).addClusterLocalityStats(
173173
lbConfig.getLrsLoadReportingServerName(),
174174
lbConfig.getClusterName(),
175175
lbConfig.getEdsServiceName(),

packages/grpc-js-xds/src/resolver-xds.ts

+19-7
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from './environment'
4848
import Filter = experimental.Filter;
4949
import FilterFactory = experimental.FilterFactory;
5050
import RetryPolicy = experimental.RetryPolicy;
51+
import { validateBootstrapConfig } from './xds-bootstrap';
5152

5253
const TRACER_NAME = 'xds_resolver';
5354

@@ -210,6 +211,8 @@ function getDefaultRetryMaxInterval(baseInterval: string): string {
210211
return `${Number.parseFloat(baseInterval.substring(0, baseInterval.length - 1)) * 10}s`;
211212
}
212213

214+
const BOOTSTRAP_CONFIG_KEY = 'grpc.TEST_ONLY_DO_NOT_USE_IN_PROD.xds_bootstrap_config';
215+
213216
const RETRY_CODES: {[key: string]: status} = {
214217
'cancelled': status.CANCELLED,
215218
'deadline-exceeded': status.DEADLINE_EXCEEDED,
@@ -238,11 +241,20 @@ class XdsResolver implements Resolver {
238241

239242
private ldsHttpFilterConfigs: {name: string, config: HttpFilterConfig}[] = [];
240243

244+
private xdsClient: XdsClient;
245+
241246
constructor(
242247
private target: GrpcUri,
243248
private listener: ResolverListener,
244249
private channelOptions: ChannelOptions
245250
) {
251+
if (channelOptions[BOOTSTRAP_CONFIG_KEY]) {
252+
const parsedConfig = JSON.parse(channelOptions[BOOTSTRAP_CONFIG_KEY]);
253+
const validatedConfig = validateBootstrapConfig(parsedConfig);
254+
this.xdsClient = new XdsClient(validatedConfig);
255+
} else {
256+
this.xdsClient = getSingletonXdsClient();
257+
}
246258
this.ldsWatcher = {
247259
onValidUpdate: (update: Listener__Output) => {
248260
const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, update.api_listener!.api_listener!.value);
@@ -267,16 +279,16 @@ class XdsResolver implements Resolver {
267279
const routeConfigName = httpConnectionManager.rds!.route_config_name;
268280
if (this.latestRouteConfigName !== routeConfigName) {
269281
if (this.latestRouteConfigName !== null) {
270-
getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher);
282+
this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher);
271283
}
272-
getSingletonXdsClient().addRouteWatcher(httpConnectionManager.rds!.route_config_name, this.rdsWatcher);
284+
this.xdsClient.addRouteWatcher(httpConnectionManager.rds!.route_config_name, this.rdsWatcher);
273285
this.latestRouteConfigName = routeConfigName;
274286
}
275287
break;
276288
}
277289
case 'route_config':
278290
if (this.latestRouteConfigName) {
279-
getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher);
291+
this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher);
280292
}
281293
this.handleRouteConfig(httpConnectionManager.route_config!);
282294
break;
@@ -546,7 +558,7 @@ class XdsResolver implements Resolver {
546558
methodConfig: [],
547559
loadBalancingConfig: [lbPolicyConfig]
548560
}
549-
this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {});
561+
this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {xdsClient: this.xdsClient});
550562
}
551563

552564
private reportResolutionError(reason: string) {
@@ -563,15 +575,15 @@ class XdsResolver implements Resolver {
563575
// Wait until updateResolution is called once to start the xDS requests
564576
if (!this.isLdsWatcherActive) {
565577
trace('Starting resolution for target ' + uriToString(this.target));
566-
getSingletonXdsClient().addListenerWatcher(this.target.path, this.ldsWatcher);
578+
this.xdsClient.addListenerWatcher(this.target.path, this.ldsWatcher);
567579
this.isLdsWatcherActive = true;
568580
}
569581
}
570582

571583
destroy() {
572-
getSingletonXdsClient().removeListenerWatcher(this.target.path, this.ldsWatcher);
584+
this.xdsClient.removeListenerWatcher(this.target.path, this.ldsWatcher);
573585
if (this.latestRouteConfigName) {
574-
getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher);
586+
this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher);
575587
}
576588
}
577589

packages/grpc-js-xds/src/xds-bootstrap.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ function validateNode(obj: any): Node {
231231
return result;
232232
}
233233

234-
function validateBootstrapFile(obj: any): BootstrapInfo {
234+
export function validateBootstrapConfig(obj: any): BootstrapInfo {
235235
return {
236236
xdsServers: obj.xds_servers.map(validateXdsServerConfig),
237237
node: validateNode(obj.node),
@@ -265,7 +265,7 @@ export async function loadBootstrapInfo(): Promise<BootstrapInfo> {
265265
}
266266
try {
267267
const parsedFile = JSON.parse(data);
268-
resolve(validateBootstrapFile(parsedFile));
268+
resolve(validateBootstrapConfig(parsedFile));
269269
} catch (e) {
270270
reject(
271271
new Error(
@@ -290,7 +290,7 @@ export async function loadBootstrapInfo(): Promise<BootstrapInfo> {
290290
if (bootstrapConfig) {
291291
try {
292292
const parsedConfig = JSON.parse(bootstrapConfig);
293-
const loadedBootstrapInfoValue = validateBootstrapFile(parsedConfig);
293+
const loadedBootstrapInfoValue = validateBootstrapConfig(parsedConfig);
294294
loadedBootstrapInfo = Promise.resolve(loadedBootstrapInfoValue);
295295
} catch (e) {
296296
throw new Error(

packages/grpc-js-xds/src/xds-client.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util';
2121
import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials, Channel, connectivityState } from '@grpc/grpc-js';
2222
import * as adsTypes from './generated/ads';
2323
import * as lrsTypes from './generated/lrs';
24-
import { loadBootstrapInfo } from './xds-bootstrap';
24+
import { BootstrapInfo, loadBootstrapInfo } from './xds-bootstrap';
2525
import { Node } from './generated/envoy/config/core/v3/Node';
2626
import { AggregatedDiscoveryServiceClient } from './generated/envoy/service/discovery/v3/AggregatedDiscoveryService';
2727
import { DiscoveryRequest } from './generated/envoy/service/discovery/v3/DiscoveryRequest';
@@ -276,7 +276,7 @@ export class XdsClient {
276276
private adsBackoff: BackoffTimeout;
277277
private lrsBackoff: BackoffTimeout;
278278

279-
constructor() {
279+
constructor(bootstrapInfoOverride?: BootstrapInfo) {
280280
const edsState = new EdsState(() => {
281281
this.updateNames('eds');
282282
});
@@ -310,7 +310,15 @@ export class XdsClient {
310310
});
311311
this.lrsBackoff.unref();
312312

313-
Promise.all([loadBootstrapInfo(), loadAdsProtos()]).then(
313+
async function getBootstrapInfo(): Promise<BootstrapInfo> {
314+
if (bootstrapInfoOverride) {
315+
return bootstrapInfoOverride;
316+
} else {
317+
return loadBootstrapInfo();
318+
}
319+
}
320+
321+
Promise.all([getBootstrapInfo(), loadAdsProtos()]).then(
314322
([bootstrapInfo, protoDefinitions]) => {
315323
if (this.hasShutdown) {
316324
return;

packages/grpc-js-xds/src/xds-stream-state/eds-state.ts

+8
Original file line numberDiff line numberDiff line change
@@ -61,27 +61,33 @@ export class EdsState extends BaseXdsStreamState<ClusterLoadAssignment__Output>
6161
const priorityTotalWeights: Map<number, number> = new Map();
6262
for (const endpoint of message.endpoints) {
6363
if (!endpoint.locality) {
64+
trace('EDS validation: endpoint locality unset');
6465
return false;
6566
}
6667
for (const {locality, priority} of seenLocalities) {
6768
if (localitiesEqual(endpoint.locality, locality) && endpoint.priority === priority) {
69+
trace('EDS validation: endpoint locality duplicated: ' + JSON.stringify(locality) + ', priority=' + priority);
6870
return false;
6971
}
7072
}
7173
seenLocalities.push({locality: endpoint.locality, priority: endpoint.priority});
7274
for (const lb of endpoint.lb_endpoints) {
7375
const socketAddress = lb.endpoint?.address?.socket_address;
7476
if (!socketAddress) {
77+
trace('EDS validation: endpoint socket_address not set');
7578
return false;
7679
}
7780
if (socketAddress.port_specifier !== 'port_value') {
81+
trace('EDS validation: socket_address.port_specifier !== "port_value"');
7882
return false;
7983
}
8084
if (!(isIPv4(socketAddress.address) || isIPv6(socketAddress.address))) {
85+
trace('EDS validation: address not a valid IPv4 or IPv6 address: ' + socketAddress.address);
8186
return false;
8287
}
8388
for (const address of seenAddresses) {
8489
if (addressesEqual(socketAddress, address)) {
90+
trace('EDS validation: duplicate address seen: ' + address);
8591
return false;
8692
}
8793
}
@@ -91,11 +97,13 @@ export class EdsState extends BaseXdsStreamState<ClusterLoadAssignment__Output>
9197
}
9298
for (const totalWeight of priorityTotalWeights.values()) {
9399
if (totalWeight > UINT32_MAX) {
100+
trace('EDS validation: total weight > UINT32_MAX')
94101
return false;
95102
}
96103
}
97104
for (const priority of priorityTotalWeights.keys()) {
98105
if (priority > 0 && !priorityTotalWeights.has(priority - 1)) {
106+
trace('EDS validation: priorities not contiguous');
99107
return false;
100108
}
101109
}

0 commit comments

Comments
 (0)