Skip to content

Commit fe74b60

Browse files
committed
grpc-js-xds: Add support for pick_first in xDS config
1 parent 6567f8d commit fe74b60

File tree

6 files changed

+107
-1
lines changed

6 files changed

+107
-1
lines changed

packages/grpc-js-xds/gulpfile.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const compile = checkTask(() => execNpmCommand('compile'));
6363
const runTests = checkTask(() => {
6464
process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION = 'true';
6565
process.env.GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG = 'true';
66+
process.env.GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG = 'true';
6667
if (Number(process.versions.node.split('.')[0]) > 14) {
6768
process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH = 'true';
6869
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETR
2121
export const EXPERIMENTAL_FEDERATION = (process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION ?? 'false') === 'true';
2222
export const EXPERIMENTAL_CUSTOM_LB_CONFIG = (process.env.GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG ?? 'false') === 'true';
2323
export const EXPERIMENTAL_RING_HASH = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH ?? 'false') === 'true';
24+
export const EXPERIMENTAL_PICK_FIRST = (process.env.GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG ?? 'false') === 'true';

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import * as fault_injection_filter from './http-filter/fault-injection-filter';
2929
import * as csds from './csds';
3030
import * as round_robin_lb from './lb-policy-registry/round-robin';
3131
import * as typed_struct_lb from './lb-policy-registry/typed-struct';
32+
import * as pick_first_lb from './lb-policy-registry/pick-first';
3233

3334
/**
3435
* Register the "xds:" name scheme with the @grpc/grpc-js library.
@@ -48,4 +49,5 @@ export function register() {
4849
csds.setup();
4950
round_robin_lb.setup();
5051
typed_struct_lb.setup();
52+
pick_first_lb.setup();
5153
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
18+
// https://github.com/grpc/proposal/blob/master/A62-pick-first.md#pick_first-via-xds-1
19+
20+
import { LoadBalancingConfig } from "@grpc/grpc-js";
21+
import { LoadBalancingPolicy__Output } from "../generated/envoy/config/cluster/v3/LoadBalancingPolicy";
22+
import { TypedExtensionConfig__Output } from "../generated/envoy/config/core/v3/TypedExtensionConfig";
23+
import { loadProtosWithOptionsSync } from "@grpc/proto-loader/build/src/util";
24+
import { Any__Output } from "../generated/google/protobuf/Any";
25+
import { PickFirst__Output } from "../generated/envoy/extensions/load_balancing_policies/pick_first/v3/PickFirst";
26+
import { EXPERIMENTAL_PICK_FIRST } from "../environment";
27+
import { registerLbPolicy } from "../lb-policy-registry";
28+
29+
const PICK_FIRST_TYPE_URL = 'type.googleapis.com/envoy.extensions.load_balancing_policies.pick_first.v3.PickFirst';
30+
31+
const resourceRoot = loadProtosWithOptionsSync([
32+
'envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto'], {
33+
keepCase: true,
34+
includeDirs: [
35+
// Paths are relative to src/build/lb-policy-registry
36+
__dirname + '/../../../deps/envoy-api/',
37+
__dirname + '/../../../deps/xds/',
38+
__dirname + '/../../../deps/protoc-gen-validate'
39+
],
40+
}
41+
);
42+
43+
const toObjectOptions = {
44+
longs: String,
45+
enums: String,
46+
defaults: true,
47+
oneofs: true
48+
}
49+
50+
function decodePickFirstConfig(message: Any__Output): PickFirst__Output {
51+
const name = message.type_url.substring(message.type_url.lastIndexOf('/') + 1);
52+
const type = resourceRoot.lookup(name);
53+
if (type) {
54+
const decodedMessage = (type as any).decode(message.value);
55+
return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as PickFirst__Output;
56+
} else {
57+
throw new Error(`TypedStruct parsing error: unexpected type URL ${message.type_url}`);
58+
}
59+
}
60+
61+
function convertToLoadBalancingPolicy(protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig | null {
62+
if (protoPolicy.typed_config?.type_url !== PICK_FIRST_TYPE_URL) {
63+
throw new Error(`Pick first LB policy parsing error: unexpected type URL ${protoPolicy.typed_config?.type_url}`);
64+
}
65+
const pickFirstMessage = decodePickFirstConfig(protoPolicy.typed_config);
66+
return {
67+
pick_first: {
68+
shuffleAddressList: pickFirstMessage.shuffle_address_list
69+
}
70+
};
71+
}
72+
73+
export function setup() {
74+
if (EXPERIMENTAL_PICK_FIRST) {
75+
registerLbPolicy(PICK_FIRST_TYPE_URL, convertToLoadBalancingPolicy);
76+
}
77+
}

packages/grpc-js-xds/test/test-custom-lb-policies.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import PickResultType = experimental.PickResultType;
3838
import createChildChannelControlHelper = experimental.createChildChannelControlHelper;
3939
import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig;
4040
import registerLoadBalancerType = experimental.registerLoadBalancerType;
41+
import { PickFirst } from "../src/generated/envoy/extensions/load_balancing_policies/pick_first/v3/PickFirst";
4142

4243
const LB_POLICY_NAME = 'test.RpcBehaviorLoadBalancer';
4344

@@ -297,5 +298,28 @@ describe('Custom LB policies', () => {
297298
done();
298299
});
299300
}, reason => done(reason));
300-
})
301+
});
302+
it('Should handle pick_first', done => {
303+
const lbPolicy: PickFirst & AnyExtension = {
304+
'@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.pick_first.v3.PickFirst',
305+
shuffle_address_list: true
306+
};
307+
const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}], lbPolicy);
308+
const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]);
309+
routeGroup.startAllBackends().then(() => {
310+
xdsServer.setEdsResource(cluster.getEndpointConfig());
311+
xdsServer.setCdsResource(cluster.getClusterConfig());
312+
xdsServer.setRdsResource(routeGroup.getRouteConfiguration());
313+
xdsServer.setLdsResource(routeGroup.getListener());
314+
xdsServer.addResponseListener((typeUrl, responseState) => {
315+
if (responseState.state === 'NACKED') {
316+
client.stopCalls();
317+
assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`);
318+
}
319+
})
320+
client = XdsTestClient.createFromServer('listener1', xdsServer);
321+
client.sendOneCall(done);
322+
}, reason => done(reason));
323+
});
324+
301325
});

packages/grpc-js-xds/test/xds-server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const loadedProtos = loadPackageDefinition(loadSync(
4545
'envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.proto',
4646
'envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto',
4747
'envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto',
48+
'envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto',
4849
'xds/type/v3/typed_struct.proto'
4950
],
5051
{

0 commit comments

Comments
 (0)