Skip to content

Commit 4a0f4cf

Browse files
committed
Merge branch 'master' into grpc-js-xds_dependency_manager_watcher_updates
2 parents 9e35cac + e6da4ad commit 4a0f4cf

31 files changed

+1182
-844
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import PickResult = grpc.experimental.PickResult;
4141
import PickResultType = grpc.experimental.PickResultType;
4242
import createChildChannelControlHelper = grpc.experimental.createChildChannelControlHelper;
4343
import parseLoadBalancingConfig = grpc.experimental.parseLoadBalancingConfig;
44+
import StatusOr = grpc.experimental.StatusOr;
4445
import { ChannelOptions } from '@grpc/grpc-js';
4546

4647
grpc_xds.register();
@@ -100,12 +101,12 @@ class RpcBehaviorLoadBalancer implements LoadBalancer {
100101
});
101102
this.child = new ChildLoadBalancerHandler(childChannelControlHelper);
102103
}
103-
updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, options: ChannelOptions): void {
104+
updateAddressList(endpointList: StatusOr<Endpoint[]>, lbConfig: TypedLoadBalancingConfig, options: ChannelOptions, resolutionNote: string): boolean {
104105
if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) {
105-
return;
106+
return false;
106107
}
107108
this.latestConfig = lbConfig;
108-
this.child.updateAddressList(endpointList, RPC_BEHAVIOR_CHILD_CONFIG, options);
109+
return this.child.updateAddressList(endpointList, RPC_BEHAVIOR_CHILD_CONFIG, options, resolutionNote);
109110
}
110111
exitIdle(): void {
111112
this.child.exitIdle();

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

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import ChannelControlHelper = experimental.ChannelControlHelper;
2626
import registerLoadBalancerType = experimental.registerLoadBalancerType;
2727
import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
2828
import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig;
29+
import StatusOr = experimental.StatusOr;
30+
import statusOrFromValue = experimental.statusOrFromValue;
2931
import { XdsConfig } from './xds-dependency-manager';
3032
import { LocalityEndpoint, PriorityChildRaw } from './load-balancer-priority';
3133
import { Locality__Output } from './generated/envoy/config/core/v3/Locality';
@@ -205,7 +207,7 @@ function getLeafClusters(xdsConfig: XdsConfig, rootCluster: string, depth = 0):
205207
if (!maybeClusterConfig) {
206208
return [];
207209
}
208-
if (!maybeClusterConfig.success) {
210+
if (!maybeClusterConfig.ok) {
209211
return [rootCluster];
210212
}
211213
if (maybeClusterConfig.value.children.type === 'aggregate') {
@@ -240,26 +242,27 @@ export class CdsLoadBalancer implements LoadBalancer {
240242
}
241243

242244
updateAddressList(
243-
endpointList: Endpoint[],
245+
endpointList: StatusOr<Endpoint[]>,
244246
lbConfig: TypedLoadBalancingConfig,
245-
options: ChannelOptions
246-
): void {
247+
options: ChannelOptions,
248+
resolutionNote: string
249+
): boolean {
247250
if (!(lbConfig instanceof CdsLoadBalancingConfig)) {
248251
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig, undefined, 2));
249-
return;
252+
return false;
250253
}
251254
trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2));
252255
const xdsConfig = options[XDS_CONFIG_KEY] as XdsConfig;
253256
const clusterName = lbConfig.getCluster();
254257
const maybeClusterConfig = xdsConfig.clusters.get(clusterName);
255258
if (!maybeClusterConfig) {
256259
trace('Received update with no config for cluster ' + clusterName);
257-
return;
260+
return false;
258261
}
259-
if (!maybeClusterConfig.success) {
262+
if (!maybeClusterConfig.ok) {
260263
this.childBalancer.destroy();
261264
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker(maybeClusterConfig.error), maybeClusterConfig.error.details);
262-
return;
265+
return true;
263266
}
264267
const clusterConfig = maybeClusterConfig.value;
265268

@@ -270,8 +273,8 @@ export class CdsLoadBalancer implements LoadBalancer {
270273
} catch (e) {
271274
trace('xDS config parsing failed with error ' + (e as Error).message);
272275
const errorMessage = `xDS config parsing failed with error ${(e as Error).message}`;
273-
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
274-
return;
276+
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `${errorMessage} Resolution note: ${resolutionNote}`}), errorMessage);
277+
return true;
275278
}
276279
const priorityChildren: {[name: string]: PriorityChildRaw} = {};
277280
for (const cluster of leafClusters) {
@@ -296,16 +299,16 @@ export class CdsLoadBalancer implements LoadBalancer {
296299
} catch (e) {
297300
trace('LB policy config parsing failed with error ' + (e as Error).message);
298301
const errorMessage = `LB policy config parsing failed with error ${(e as Error).message}`;
299-
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
300-
return;
302+
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `${errorMessage} Resolution note: ${resolutionNote}`}), errorMessage);
303+
return true;
301304
}
302-
this.childBalancer.updateAddressList(endpointList, typedChildConfig, {...options, [ROOT_CLUSTER_KEY]: clusterName});
305+
this.childBalancer.updateAddressList(endpointList, typedChildConfig, {...options, [ROOT_CLUSTER_KEY]: clusterName}, resolutionNote);
303306
} else {
304307
if (!clusterConfig.children.endpoints) {
305308
trace('Received update with no resolved endpoints for cluster ' + clusterName);
306309
const errorMessage = `Cluster ${clusterName} resolution failed: ${clusterConfig.children.resolutionNote}`;
307310
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
308-
return;
311+
return false;
309312
}
310313
const newPriorityNames: string[] = [];
311314
const newLocalityPriorities = new Map<string, number>();
@@ -317,7 +320,7 @@ export class CdsLoadBalancer implements LoadBalancer {
317320
if (AGGREGATE_CLUSTER_BACKWARDS_COMPAT) {
318321
if (typeof options[ROOT_CLUSTER_KEY] === 'string') {
319322
const maybeRootClusterConfig = xdsConfig.clusters.get(options[ROOT_CLUSTER_KEY]);
320-
if (maybeRootClusterConfig?.success) {
323+
if (maybeRootClusterConfig?.ok) {
321324
endpointPickingPolicy = maybeRootClusterConfig.value.cluster.lbPolicyConfig;
322325
}
323326
}
@@ -409,26 +412,26 @@ export class CdsLoadBalancer implements LoadBalancer {
409412
typedChildConfig = parseLoadBalancingConfig(childConfig);
410413
} catch (e) {
411414
trace('LB policy config parsing failed with error ' + (e as Error).message);
412-
const errorMessage = `LB policy config parsing failed with error ${(e as Error).message}`;
415+
const errorMessage = `LB policy config parsing failed with error ${(e as Error).message}. Resolution note: ${resolutionNote}`;
413416
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
414-
return;
417+
return false;
415418
}
416419
const childOptions: ChannelOptions = {...options};
417420
if (clusterConfig.cluster.securityUpdate) {
418421
const securityUpdate = clusterConfig.cluster.securityUpdate;
419422
const xdsClient = options[XDS_CLIENT_KEY] as XdsClient;
420423
const caCertProvider = xdsClient.getCertificateProvider(securityUpdate.caCertificateProviderInstance);
421424
if (!caCertProvider) {
422-
const errorMessage = `Cluster ${clusterName} configured with CA certificate provider ${securityUpdate.caCertificateProviderInstance} not in bootstrap`;
425+
const errorMessage = `Cluster ${clusterName} configured with CA certificate provider ${securityUpdate.caCertificateProviderInstance} not in bootstrap. Resolution note: ${resolutionNote}`;
423426
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
424-
return;
427+
return false;
425428
}
426429
if (securityUpdate.identityCertificateProviderInstance) {
427430
const identityCertProvider = xdsClient.getCertificateProvider(securityUpdate.identityCertificateProviderInstance);
428431
if (!identityCertProvider) {
429-
const errorMessage = `Cluster ${clusterName} configured with identity certificate provider ${securityUpdate.identityCertificateProviderInstance} not in bootstrap`;
432+
const errorMessage = `Cluster ${clusterName} configured with identity certificate provider ${securityUpdate.identityCertificateProviderInstance} not in bootstrap. Resolution note: ${resolutionNote}`;
430433
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
431-
return;
434+
return false;
432435
}
433436
childOptions[IDENTITY_CERT_PROVIDER_KEY] = identityCertProvider;
434437
}
@@ -440,8 +443,9 @@ export class CdsLoadBalancer implements LoadBalancer {
440443
trace('Configured subject alternative name matcher: ' + sanMatcher);
441444
childOptions[SAN_MATCHER_KEY] = this.latestSanMatcher;
442445
}
443-
this.childBalancer.updateAddressList(childEndpointList, typedChildConfig, childOptions);
446+
this.childBalancer.updateAddressList(statusOrFromValue(childEndpointList), typedChildConfig, childOptions, resolutionNote);
444447
}
448+
return true;
445449
}
446450
exitIdle(): void {
447451
this.childBalancer.exitIdle();

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

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import QueuePicker = experimental.QueuePicker;
2727
import UnavailablePicker = experimental.UnavailablePicker;
2828
import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler;
2929
import selectLbConfigFromList = experimental.selectLbConfigFromList;
30+
import StatusOr = experimental.StatusOr;
31+
import statusOrFromValue = experimental.statusOrFromValue;
3032
import { Locality__Output } from './generated/envoy/config/core/v3/Locality';
3133

3234
const TRACER_NAME = 'priority';
@@ -155,9 +157,10 @@ class PriorityLoadBalancingConfig implements TypedLoadBalancingConfig {
155157

156158
interface PriorityChildBalancer {
157159
updateAddressList(
158-
endpointList: Endpoint[],
160+
endpointList: StatusOr<Endpoint[]>,
159161
lbConfig: TypedLoadBalancingConfig,
160-
attributes: { [key: string]: unknown }
162+
attributes: { [key: string]: unknown },
163+
resolutionNote: string
161164
): void;
162165
exitIdle(): void;
163166
resetBackoff(): void;
@@ -240,11 +243,12 @@ export class PriorityLoadBalancer implements LoadBalancer {
240243
}
241244

242245
updateAddressList(
243-
endpointList: Endpoint[],
246+
endpointList: StatusOr<Endpoint[]>,
244247
lbConfig: TypedLoadBalancingConfig,
245-
attributes: { [key: string]: unknown }
248+
attributes: { [key: string]: unknown },
249+
resolutionNote: string
246250
): void {
247-
this.childBalancer.updateAddressList(endpointList, lbConfig, attributes);
251+
this.childBalancer.updateAddressList(endpointList, lbConfig, attributes, resolutionNote);
248252
}
249253

250254
exitIdle() {
@@ -332,6 +336,8 @@ export class PriorityLoadBalancer implements LoadBalancer {
332336

333337
private updatesPaused = false;
334338

339+
private latestResolutionNote: string = '';
340+
335341
constructor(private channelControlHelper: ChannelControlHelper) {}
336342

337343
private updateState(state: ConnectivityState, picker: Picker, errorMessage: string | null) {
@@ -401,9 +407,10 @@ export class PriorityLoadBalancer implements LoadBalancer {
401407
child = new this.PriorityChildImpl(this, childName, childUpdate.ignoreReresolutionRequests);
402408
this.children.set(childName, child);
403409
child.updateAddressList(
404-
childUpdate.subchannelAddress,
410+
statusOrFromValue(childUpdate.subchannelAddress),
405411
childUpdate.lbConfig,
406-
this.latestOptions
412+
this.latestOptions,
413+
this.latestResolutionNote
407414
);
408415
} else {
409416
/* We're going to try to use this child, so reactivate it if it has been
@@ -440,14 +447,21 @@ export class PriorityLoadBalancer implements LoadBalancer {
440447
}
441448

442449
updateAddressList(
443-
endpointList: Endpoint[],
450+
endpointList: StatusOr<Endpoint[]>,
444451
lbConfig: TypedLoadBalancingConfig,
445-
options: ChannelOptions
446-
): void {
452+
options: ChannelOptions,
453+
resolutionNote: string
454+
): boolean {
447455
if (!(lbConfig instanceof PriorityLoadBalancingConfig)) {
448456
// Reject a config of the wrong type
449457
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
450-
return;
458+
return false;
459+
}
460+
if (!endpointList.ok) {
461+
if (this.latestUpdates.size === 0) {
462+
this.updateState(ConnectivityState.TRANSIENT_FAILURE, new UnavailablePicker(endpointList.error), endpointList.error.details);
463+
}
464+
return true;
451465
}
452466
/* For each address, the first element of its localityPath array determines
453467
* which child it belongs to. So we bucket those addresses by that first
@@ -457,14 +471,14 @@ export class PriorityLoadBalancer implements LoadBalancer {
457471
string,
458472
LocalityEndpoint[]
459473
>();
460-
for (const endpoint of endpointList) {
474+
for (const endpoint of endpointList.value) {
461475
if (!isLocalityEndpoint(endpoint)) {
462476
// Reject address that cannot be prioritized
463-
return;
477+
return false;
464478
}
465479
if (endpoint.localityPath.length < 1) {
466480
// Reject address that cannot be prioritized
467-
return;
481+
return false;
468482
}
469483
const childName = endpoint.localityPath[0];
470484
const childAddress: LocalityEndpoint = {
@@ -495,9 +509,10 @@ export class PriorityLoadBalancer implements LoadBalancer {
495509
const existingChild = this.children.get(childName);
496510
if (existingChild !== undefined) {
497511
existingChild.updateAddressList(
498-
childAddresses,
512+
statusOrFromValue(childAddresses),
499513
childConfig.config,
500-
options
514+
options,
515+
resolutionNote
501516
);
502517
}
503518
}
@@ -509,7 +524,9 @@ export class PriorityLoadBalancer implements LoadBalancer {
509524
}
510525
}
511526
this.updatesPaused = false;
527+
this.latestResolutionNote = resolutionNote;
512528
this.choosePriority();
529+
return true;
513530
}
514531
exitIdle(): void {
515532
if (this.currentPriority !== null) {

packages/grpc-js-xds/src/load-balancer-ring-hash.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import UnavailablePicker = experimental.UnavailablePicker;
3131
import subchannelAddressToString = experimental.subchannelAddressToString;
3232
import registerLoadBalancerType = experimental.registerLoadBalancerType;
3333
import EndpointMap = experimental.EndpointMap;
34+
import StatusOr = experimental.StatusOr;
3435
import { loadXxhashApi, xxhashApi } from './xxhash';
3536
import { EXPERIMENTAL_RING_HASH } from './environment';
3637
import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util';
@@ -401,26 +402,44 @@ class RingHashLoadBalancer implements LoadBalancer {
401402
}
402403

403404
updateAddressList(
404-
endpointList: Endpoint[],
405+
endpointList: StatusOr<Endpoint[]>,
405406
lbConfig: TypedLoadBalancingConfig,
406-
options: ChannelOptions
407-
): void {
407+
options: ChannelOptions,
408+
resolutionNote: string
409+
): boolean {
408410
if (!(lbConfig instanceof RingHashLoadBalancingConfig)) {
409411
trace('Discarding address update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
410-
return;
412+
return false;
413+
}
414+
if (!endpointList.ok) {
415+
if (this.ring.length === 0) {
416+
this.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker(endpointList.error), endpointList.error.details);
417+
}
418+
return true;
419+
}
420+
if (endpointList.value.length === 0) {
421+
for (const ringEntry of this.ring) {
422+
ringEntry.leafBalancer.destroy();
423+
}
424+
this.ring = [];
425+
this.leafMap.clear();
426+
this.leafWeightMap.clear();
427+
const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
428+
this.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
429+
return false;
411430
}
412431
trace('Received update with config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
413432
this.updatesPaused = true;
414433
this.leafWeightMap.clear();
415434
const dedupedEndpointList: Endpoint[] = [];
416-
for (const endpoint of endpointList) {
435+
for (const endpoint of endpointList.value) {
417436
const leafBalancer = this.leafMap.get(endpoint);
418437
if (leafBalancer) {
419438
leafBalancer.updateEndpoint(endpoint, options);
420439
} else {
421440
this.leafMap.set(
422441
endpoint,
423-
new LeafLoadBalancer(endpoint, this.childChannelControlHelper, options)
442+
new LeafLoadBalancer(endpoint, this.childChannelControlHelper, options, resolutionNote)
424443
);
425444
}
426445
const weight = this.leafWeightMap.get(endpoint);
@@ -429,7 +448,7 @@ class RingHashLoadBalancer implements LoadBalancer {
429448
}
430449
this.leafWeightMap.set(endpoint, (weight ?? 0) + (isLocalityEndpoint(endpoint) ? endpoint.endpointWeight : 1));
431450
}
432-
const removedLeaves = this.leafMap.deleteMissing(endpointList);
451+
const removedLeaves = this.leafMap.deleteMissing(endpointList.value);
433452
for (const leaf of removedLeaves) {
434453
leaf.destroy();
435454
}
@@ -440,6 +459,7 @@ class RingHashLoadBalancer implements LoadBalancer {
440459
this.calculateAndUpdateState();
441460
this.maybeProactivelyConnect();
442461
});
462+
return true;
443463
}
444464
exitIdle(): void {
445465
/* This operation does not make sense here. We don't want to make the whole

0 commit comments

Comments
 (0)