Skip to content

Commit 9272aee

Browse files
authored
Merge pull request #2557 from murgatroid99/grpc-js-xds_run_custom_lb_test
grpc-js-xds: interop: add custom_lb test, reformat test list
2 parents 354bd2d + 04ef125 commit 9272aee

File tree

4 files changed

+160
-4
lines changed

4 files changed

+160
-4
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class RpcBehaviorLoadBalancer implements LoadBalancer {
9292
const childChannelControlHelper = createChildChannelControlHelper(channelControlHelper, {
9393
updateState: (connectivityState, picker) => {
9494
if (connectivityState === grpc.connectivityState.READY && this.latestConfig) {
95-
picker = new RpcBehaviorPicker(picker, this.latestConfig.getLoadBalancerName());
95+
picker = new RpcBehaviorPicker(picker, this.latestConfig.getRpcBehavior());
9696
}
9797
channelControlHelper.updateState(connectivityState, picker);
9898
}

packages/grpc-js-xds/scripts/xds_k8s_lb.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,16 @@ main() {
165165
# Run tests
166166
cd "${TEST_DRIVER_FULL_DIR}"
167167
local failed_tests=0
168-
test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection_test")
168+
test_suites=(
169+
"api_listener_test"
170+
"baseline_test"
171+
"change_backend_service_test"
172+
"custom_lb_test"
173+
"failover_test"
174+
"outlier_detection_test"
175+
"remove_neg_test"
176+
"round_robin_test"
177+
)
169178
for test in "${test_suites[@]}"; do
170179
run_test $test || (( ++failed_tests ))
171180
done

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ export class Backend {
4848
Echo(call: ServerUnaryCall<EchoRequest__Output, EchoResponse>, callback: sendUnaryData<EchoResponse>) {
4949
// call.request.params is currently ignored
5050
this.addCall();
51+
for (const behaviorEntry of call.metadata.get('rpc-behavior')) {
52+
if (typeof behaviorEntry !== 'string') {
53+
continue;
54+
}
55+
for (const behavior of behaviorEntry.split(',')) {
56+
if (behavior.startsWith('error-code-')) {
57+
const errorCode = Number(behavior.substring('error-code-'.length));
58+
callback({code: errorCode, details: 'rpc-behavior error code'});
59+
return;
60+
}
61+
}
62+
}
5163
callback(null, {message: call.request.message});
5264
}
5365

@@ -87,7 +99,7 @@ export class Backend {
8799
});
88100
});
89101
}
90-
102+
91103
getPort(): number {
92104
if (this.port === null) {
93105
throw new Error('Port not set. Backend not yet started.');
@@ -125,4 +137,4 @@ export class Backend {
125137
});
126138
});
127139
}
128-
}
140+
}

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

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,98 @@ import { XdsServer } from "./xds-server";
2424
import * as assert from 'assert';
2525
import { WrrLocality } from "../src/generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality";
2626
import { TypedStruct } from "../src/generated/xds/type/v3/TypedStruct";
27+
import { connectivityState, experimental, logVerbosity } from "@grpc/grpc-js";
28+
29+
import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
30+
import LoadBalancer = experimental.LoadBalancer;
31+
import ChannelControlHelper = experimental.ChannelControlHelper;
32+
import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler;
33+
import SubchannelAddress = experimental.SubchannelAddress;
34+
import Picker = experimental.Picker;
35+
import PickArgs = experimental.PickArgs;
36+
import PickResult = experimental.PickResult;
37+
import PickResultType = experimental.PickResultType;
38+
import createChildChannelControlHelper = experimental.createChildChannelControlHelper;
39+
import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig;
40+
import registerLoadBalancerType = experimental.registerLoadBalancerType;
41+
42+
const LB_POLICY_NAME = 'test.RpcBehaviorLoadBalancer';
43+
44+
class RpcBehaviorLoadBalancingConfig implements TypedLoadBalancingConfig {
45+
constructor(private rpcBehavior: string) {}
46+
getLoadBalancerName(): string {
47+
return LB_POLICY_NAME;
48+
}
49+
toJsonObject(): object {
50+
return {
51+
[LB_POLICY_NAME]: {
52+
'rpcBehavior': this.rpcBehavior
53+
}
54+
};
55+
}
56+
getRpcBehavior() {
57+
return this.rpcBehavior;
58+
}
59+
static createFromJson(obj: any): RpcBehaviorLoadBalancingConfig {
60+
if (!('rpcBehavior' in obj && typeof obj.rpcBehavior === 'string')) {
61+
throw new Error(`${LB_POLICY_NAME} parsing error: expected string field rpcBehavior`);
62+
}
63+
return new RpcBehaviorLoadBalancingConfig(obj.rpcBehavior);
64+
}
65+
}
66+
67+
class RpcBehaviorPicker implements Picker {
68+
constructor(private wrappedPicker: Picker, private rpcBehavior: string) {}
69+
pick(pickArgs: PickArgs): PickResult {
70+
const wrappedPick = this.wrappedPicker.pick(pickArgs);
71+
if (wrappedPick.pickResultType === PickResultType.COMPLETE) {
72+
pickArgs.metadata.add('rpc-behavior', this.rpcBehavior);
73+
}
74+
return wrappedPick;
75+
}
76+
}
77+
78+
const RPC_BEHAVIOR_CHILD_CONFIG = parseLoadBalancingConfig({round_robin: {}});
79+
80+
/**
81+
* Load balancer implementation for Custom LB policy test
82+
*/
83+
class RpcBehaviorLoadBalancer implements LoadBalancer {
84+
private child: ChildLoadBalancerHandler;
85+
private latestConfig: RpcBehaviorLoadBalancingConfig | null = null;
86+
constructor(channelControlHelper: ChannelControlHelper) {
87+
const childChannelControlHelper = createChildChannelControlHelper(channelControlHelper, {
88+
updateState: (state, picker) => {
89+
if (state === connectivityState.READY && this.latestConfig) {
90+
picker = new RpcBehaviorPicker(picker, this.latestConfig.getRpcBehavior());
91+
}
92+
channelControlHelper.updateState(state, picker);
93+
}
94+
});
95+
this.child = new ChildLoadBalancerHandler(childChannelControlHelper);
96+
}
97+
updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void {
98+
if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) {
99+
return;
100+
}
101+
this.latestConfig = lbConfig;
102+
this.child.updateAddressList(addressList, RPC_BEHAVIOR_CHILD_CONFIG, attributes);
103+
}
104+
exitIdle(): void {
105+
this.child.exitIdle();
106+
}
107+
resetBackoff(): void {
108+
this.child.resetBackoff();
109+
}
110+
destroy(): void {
111+
this.child.destroy();
112+
}
113+
getTypeName(): string {
114+
return LB_POLICY_NAME;
115+
}
116+
}
117+
118+
registerLoadBalancerType(LB_POLICY_NAME, RpcBehaviorLoadBalancer, RpcBehaviorLoadBalancingConfig);
27119

28120
describe('Custom LB policies', () => {
29121
let xdsServer: XdsServer;
@@ -163,4 +255,47 @@ describe('Custom LB policies', () => {
163255
client.sendOneCall(done);
164256
}, reason => done(reason));
165257
});
258+
it('Should handle a custom LB policy', done => {
259+
const childPolicy: TypedStruct & AnyExtension = {
260+
'@type': 'type.googleapis.com/xds.type.v3.TypedStruct',
261+
type_url: 'test.RpcBehaviorLoadBalancer',
262+
value: {
263+
fields: {
264+
rpcBehavior: {stringValue: 'error-code-15'}
265+
}
266+
}
267+
};
268+
const lbPolicy: WrrLocality & AnyExtension = {
269+
'@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality',
270+
endpoint_picking_policy: {
271+
policies: [
272+
{
273+
typed_extension_config: {
274+
name: 'child',
275+
typed_config: childPolicy
276+
}
277+
}
278+
]
279+
}
280+
};
281+
const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}], lbPolicy);
282+
const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]);
283+
routeGroup.startAllBackends().then(() => {
284+
xdsServer.setEdsResource(cluster.getEndpointConfig());
285+
xdsServer.setCdsResource(cluster.getClusterConfig());
286+
xdsServer.setRdsResource(routeGroup.getRouteConfiguration());
287+
xdsServer.setLdsResource(routeGroup.getListener());
288+
xdsServer.addResponseListener((typeUrl, responseState) => {
289+
if (responseState.state === 'NACKED') {
290+
client.stopCalls();
291+
assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`);
292+
}
293+
})
294+
client = XdsTestClient.createFromServer('listener1', xdsServer);
295+
client.sendOneCall(error => {
296+
assert.strictEqual(error?.code, 15);
297+
done();
298+
});
299+
}, reason => done(reason));
300+
})
166301
});

0 commit comments

Comments
 (0)