Skip to content

Commit 08bcbfc

Browse files
committed
grpc-js-xds: Adjust LB policy config handling for grpc-js changes
1 parent d28b9e8 commit 08bcbfc

11 files changed

+302
-256
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2023 gRPC authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { experimental } from '@grpc/grpc-js';
18+
import { Duration__Output } from './generated/google/protobuf/Duration';
19+
import Duration = experimental.Duration;
20+
21+
/**
22+
* Convert a Duration protobuf message object to a Duration object as used in
23+
* the ServiceConfig definition. The difference is that the protobuf message
24+
* defines seconds as a long, which is represented as a string in JavaScript,
25+
* and the one used in the service config defines it as a number.
26+
* @param duration
27+
*/
28+
export function protoDurationToDuration(duration: Duration__Output): Duration {
29+
return {
30+
seconds: Number.parseInt(duration.seconds),
31+
nanos: duration.nanos
32+
};
33+
}

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

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*
1616
*/
1717

18-
import { connectivityState, status, Metadata, logVerbosity, experimental } from '@grpc/grpc-js';
18+
import { connectivityState, status, Metadata, logVerbosity, experimental, LoadBalancingConfig } from '@grpc/grpc-js';
1919
import { getSingletonXdsClient, Watcher, XdsClient } from './xds-client';
2020
import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster';
2121
import SubchannelAddress = experimental.SubchannelAddress;
@@ -24,17 +24,18 @@ import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler;
2424
import LoadBalancer = experimental.LoadBalancer;
2525
import ChannelControlHelper = experimental.ChannelControlHelper;
2626
import registerLoadBalancerType = experimental.registerLoadBalancerType;
27-
import LoadBalancingConfig = experimental.LoadBalancingConfig;
28-
import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig;
27+
import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
2928
import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig;
3029
import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig;
3130
import QueuePicker = experimental.QueuePicker;
31+
import OutlierDetectionRawConfig = experimental.OutlierDetectionRawConfig;
32+
import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig;
3233
import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection';
3334
import { Duration__Output } from './generated/google/protobuf/Duration';
3435
import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment';
35-
import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler, XdsClusterResolverLoadBalancingConfig } from './load-balancer-xds-cluster-resolver';
36+
import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler } from './load-balancer-xds-cluster-resolver';
3637
import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from './resources';
37-
import { CdsUpdate, ClusterResourceType, OutlierDetectionUpdate } from './xds-resource-type/cluster-resource-type';
38+
import { CdsUpdate, ClusterResourceType } from './xds-resource-type/cluster-resource-type';
3839

3940
const TRACER_NAME = 'cds_balancer';
4041

@@ -44,7 +45,7 @@ function trace(text: string): void {
4445

4546
const TYPE_NAME = 'cds';
4647

47-
export class CdsLoadBalancingConfig implements LoadBalancingConfig {
48+
class CdsLoadBalancingConfig implements TypedLoadBalancingConfig {
4849
getLoadBalancerName(): string {
4950
return TYPE_NAME;
5051
}
@@ -72,29 +73,6 @@ export class CdsLoadBalancingConfig implements LoadBalancingConfig {
7273
}
7374
}
7475

75-
function durationToMs(duration: Duration__Output): number {
76-
return (Number(duration.seconds) * 1_000 + duration.nanos / 1_000_000) | 0;
77-
}
78-
79-
function translateOutlierDetectionConfig(outlierDetection: OutlierDetectionUpdate | undefined): OutlierDetectionLoadBalancingConfig | undefined {
80-
if (!EXPERIMENTAL_OUTLIER_DETECTION) {
81-
return undefined;
82-
}
83-
if (!outlierDetection) {
84-
/* No-op outlier detection config, with all fields unset. */
85-
return new OutlierDetectionLoadBalancingConfig(null, null, null, null, null, null, []);
86-
}
87-
return new OutlierDetectionLoadBalancingConfig(
88-
outlierDetection.intervalMs,
89-
outlierDetection.baseEjectionTimeMs,
90-
outlierDetection.maxEjectionTimeMs,
91-
outlierDetection.maxEjectionPercent,
92-
outlierDetection.successRateConfig,
93-
outlierDetection.failurePercentageConfig,
94-
[]
95-
);
96-
}
97-
9876
interface ClusterEntry {
9977
watcher: Watcher<CdsUpdate>;
10078
latestUpdate?: CdsUpdate;
@@ -133,16 +111,16 @@ function generateDiscoverymechanismForCdsUpdate(config: CdsUpdate): DiscoveryMec
133111
type: config.type,
134112
eds_service_name: config.edsServiceName,
135113
dns_hostname: config.dnsHostname,
136-
outlier_detection: translateOutlierDetectionConfig(config.outlierDetectionUpdate)
114+
outlier_detection: config.outlierDetectionUpdate
137115
};
138116
}
139117

140118
const RECURSION_DEPTH_LIMIT = 15;
141119

142120
/**
143121
* Prerequisite: isClusterTreeFullyUpdated(tree, root)
144-
* @param tree
145-
* @param root
122+
* @param tree
123+
* @param root
146124
*/
147125
function getDiscoveryMechanismList(tree: ClusterTree, root: string): DiscoveryMechanism[] {
148126
const visited = new Set<string>();
@@ -189,6 +167,11 @@ export class CdsLoadBalancer implements LoadBalancer {
189167
this.childBalancer = new XdsClusterResolverChildPolicyHandler(channelControlHelper);
190168
}
191169

170+
private reportError(errorMessage: string) {
171+
trace('CDS cluster reporting error ' + errorMessage);
172+
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage, metadata: new Metadata()}));
173+
}
174+
192175
private addCluster(cluster: string) {
193176
if (cluster in this.clusterTree) {
194177
return;
@@ -208,19 +191,28 @@ export class CdsLoadBalancer implements LoadBalancer {
208191
try {
209192
discoveryMechanismList = getDiscoveryMechanismList(this.clusterTree, this.latestConfig!.getCluster());
210193
} catch (e) {
211-
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()}));
194+
this.reportError((e as Error).message);
195+
return;
196+
}
197+
const clusterResolverConfig: LoadBalancingConfig = {
198+
xds_cluster_resolver: {
199+
discovery_mechanisms: discoveryMechanismList,
200+
locality_picking_policy: [],
201+
endpoint_picking_policy: []
202+
}
203+
};
204+
let parsedClusterResolverConfig: TypedLoadBalancingConfig;
205+
try {
206+
parsedClusterResolverConfig = parseLoadBalancingConfig(clusterResolverConfig);
207+
} catch (e) {
208+
this.reportError(`CDS cluster ${this.latestConfig?.getCluster()} child config parsing failed with error ${(e as Error).message}`);
212209
return;
213210
}
214-
const clusterResolverConfig = new XdsClusterResolverLoadBalancingConfig(
215-
discoveryMechanismList,
216-
[],
217-
[]
218-
);
219211
trace('Child update config: ' + JSON.stringify(clusterResolverConfig));
220212
this.updatedChild = true;
221213
this.childBalancer.updateAddressList(
222214
[],
223-
clusterResolverConfig,
215+
parsedClusterResolverConfig,
224216
this.latestAttributes
225217
);
226218
}
@@ -231,20 +223,13 @@ export class CdsLoadBalancer implements LoadBalancer {
231223
this.clusterTree[cluster].latestUpdate = undefined;
232224
this.clusterTree[cluster].children = [];
233225
}
234-
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `CDS resource ${cluster} does not exist`, metadata: new Metadata()}));
226+
this.reportError(`CDS resource ${cluster} does not exist`);
235227
this.childBalancer.destroy();
236228
},
237229
onError: (statusObj) => {
238230
if (!this.updatedChild) {
239231
trace('Transitioning to transient failure due to onError update for cluster' + cluster);
240-
this.channelControlHelper.updateState(
241-
connectivityState.TRANSIENT_FAILURE,
242-
new UnavailablePicker({
243-
code: status.UNAVAILABLE,
244-
details: `xDS request failed with error ${statusObj.details}`,
245-
metadata: new Metadata(),
246-
})
247-
);
232+
this.reportError(`xDS request failed with error ${statusObj.details}`);
248233
}
249234
}
250235
});
@@ -275,7 +260,7 @@ export class CdsLoadBalancer implements LoadBalancer {
275260

276261
updateAddressList(
277262
addressList: SubchannelAddress[],
278-
lbConfig: LoadBalancingConfig,
263+
lbConfig: TypedLoadBalancingConfig,
279264
attributes: { [key: string]: unknown }
280265
): void {
281266
if (!(lbConfig instanceof CdsLoadBalancingConfig)) {

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

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ import { XdsClusterLocalityStats, XdsClient, getSingletonXdsClient } from './xds
2222
import LoadBalancer = experimental.LoadBalancer;
2323
import ChannelControlHelper = experimental.ChannelControlHelper;
2424
import registerLoadBalancerType = experimental.registerLoadBalancerType;
25-
import getFirstUsableConfig = experimental.getFirstUsableConfig;
2625
import SubchannelAddress = experimental.SubchannelAddress;
27-
import LoadBalancingConfig = experimental.LoadBalancingConfig;
2826
import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler;
2927
import Picker = experimental.Picker;
3028
import PickArgs = experimental.PickArgs;
@@ -34,11 +32,12 @@ import Filter = experimental.Filter;
3432
import BaseFilter = experimental.BaseFilter;
3533
import FilterFactory = experimental.FilterFactory;
3634
import Call = experimental.CallStream;
37-
import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig
35+
import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
36+
import selectLbConfigFromList = experimental.selectLbConfigFromList;
3837

3938
const TYPE_NAME = 'lrs';
4039

41-
export class LrsLoadBalancingConfig implements LoadBalancingConfig {
40+
class LrsLoadBalancingConfig implements TypedLoadBalancingConfig {
4241
getLoadBalancerName(): string {
4342
return TYPE_NAME;
4443
}
@@ -49,12 +48,12 @@ export class LrsLoadBalancingConfig implements LoadBalancingConfig {
4948
eds_service_name: this.edsServiceName,
5049
lrs_load_reporting_server_name: this.lrsLoadReportingServer,
5150
locality: this.locality,
52-
child_policy: this.childPolicy.map(policy => policy.toJsonObject())
51+
child_policy: [this.childPolicy.toJsonObject()]
5352
}
5453
}
5554
}
5655

57-
constructor(private clusterName: string, private edsServiceName: string, private lrsLoadReportingServer: XdsServerConfig, private locality: Locality__Output, private childPolicy: LoadBalancingConfig[]) {}
56+
constructor(private clusterName: string, private edsServiceName: string, private lrsLoadReportingServer: XdsServerConfig, private locality: Locality__Output, private childPolicy: TypedLoadBalancingConfig) {}
5857

5958
getClusterName() {
6059
return this.clusterName;
@@ -98,11 +97,15 @@ export class LrsLoadBalancingConfig implements LoadBalancingConfig {
9897
if (!('child_policy' in obj && Array.isArray(obj.child_policy))) {
9998
throw new Error('lrs config must have a child_policy array');
10099
}
100+
const childConfig = selectLbConfigFromList(obj.config);
101+
if (!childConfig) {
102+
throw new Error('lrs config child_policy parsing failed');
103+
}
101104
return new LrsLoadBalancingConfig(obj.cluster_name, obj.eds_service_name, validateXdsServerConfig(obj.lrs_load_reporting_server), {
102105
region: obj.locality.region ?? '',
103106
zone: obj.locality.zone ?? '',
104107
sub_zone: obj.locality.sub_zone ?? ''
105-
}, obj.child_policy.map(validateLoadBalancingConfig));
108+
}, childConfig);
106109
}
107110
}
108111

@@ -161,7 +164,7 @@ export class LrsLoadBalancer implements LoadBalancer {
161164

162165
updateAddressList(
163166
addressList: SubchannelAddress[],
164-
lbConfig: LoadBalancingConfig,
167+
lbConfig: TypedLoadBalancingConfig,
165168
attributes: { [key: string]: unknown }
166169
): void {
167170
if (!(lbConfig instanceof LrsLoadBalancingConfig)) {
@@ -173,11 +176,7 @@ export class LrsLoadBalancer implements LoadBalancer {
173176
lbConfig.getEdsServiceName(),
174177
lbConfig.getLocality()
175178
);
176-
const childPolicy: LoadBalancingConfig = getFirstUsableConfig(
177-
lbConfig.getChildPolicy(),
178-
true
179-
);
180-
this.childBalancer.updateAddressList(addressList, childPolicy, attributes);
179+
this.childBalancer.updateAddressList(addressList, lbConfig.getChildPolicy(), attributes);
181180
}
182181
exitIdle(): void {
183182
this.childBalancer.exitIdle();

0 commit comments

Comments
 (0)