Merge pull request #2572 from murgatroid99/grpc-js-xds_pick_first

grpc-js-xds: Add support for pick_first in xDS config
This commit is contained in:
Michael Lumish 2023-09-21 10:19:03 -07:00 committed by GitHub
commit 32c816c9d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 186 additions and 44 deletions

View File

@ -63,6 +63,7 @@ const compile = checkTask(() => execNpmCommand('compile'));
const runTests = checkTask(() => {
process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION = 'true';
process.env.GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG = 'true';
process.env.GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG = 'true';
if (Number(process.versions.node.split('.')[0]) > 14) {
process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH = 'true';
}

View File

@ -12,7 +12,7 @@
"prepare": "npm run compile",
"pretest": "npm run compile",
"posttest": "npm run check",
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/xds/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto xds/type/v3/typed_struct.proto envoy/extensions/filters/http/fault/v3/fault.proto envoy/service/status/v3/csds.proto envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto",
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/xds/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto xds/type/v3/typed_struct.proto envoy/extensions/filters/http/fault/v3/fault.proto envoy/service/status/v3/csds.proto envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto",
"generate-interop-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O interop/generated --grpcLib @grpc/grpc-js grpc/testing/test.proto",
"generate-test-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O test/generated --grpcLib @grpc/grpc-js grpc/testing/echo.proto"
},

View File

@ -21,3 +21,4 @@ export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETR
export const EXPERIMENTAL_FEDERATION = (process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION ?? 'false') === 'true';
export const EXPERIMENTAL_CUSTOM_LB_CONFIG = (process.env.GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG ?? 'false') === 'true';
export const EXPERIMENTAL_RING_HASH = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH ?? 'false') === 'true';
export const EXPERIMENTAL_PICK_FIRST = (process.env.GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG ?? 'false') === 'true';

View File

@ -0,0 +1,26 @@
// Original file: deps/envoy-api/envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto
/**
* This configuration allows the built-in PICK_FIRST LB policy to be configured
* via the LB policy extension point.
*/
export interface PickFirst {
/**
* If set to true, instructs the LB policy to shuffle the list of addresses
* received from the name resolver before attempting to connect to them.
*/
'shuffle_address_list'?: (boolean);
}
/**
* This configuration allows the built-in PICK_FIRST LB policy to be configured
* via the LB policy extension point.
*/
export interface PickFirst__Output {
/**
* If set to true, instructs the LB policy to shuffle the list of addresses
* received from the name resolver before attempting to connect to them.
*/
'shuffle_address_list': (boolean);
}

View File

@ -1,18 +1,15 @@
// Original file: null
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
import type { MigrateAnnotation as _udpa_annotations_MigrateAnnotation, MigrateAnnotation__Output as _udpa_annotations_MigrateAnnotation__Output } from '../../udpa/annotations/MigrateAnnotation';
export interface EnumOptions {
'allowAlias'?: (boolean);
'deprecated'?: (boolean);
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
'.udpa.annotations.enum_migrate'?: (_udpa_annotations_MigrateAnnotation | null);
}
export interface EnumOptions__Output {
'allowAlias': (boolean);
'deprecated': (boolean);
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
'.udpa.annotations.enum_migrate': (_udpa_annotations_MigrateAnnotation__Output | null);
}

View File

@ -1,20 +1,13 @@
// Original file: null
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
import type { MigrateAnnotation as _udpa_annotations_MigrateAnnotation, MigrateAnnotation__Output as _udpa_annotations_MigrateAnnotation__Output } from '../../udpa/annotations/MigrateAnnotation';
export interface EnumValueOptions {
'deprecated'?: (boolean);
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
'.envoy.annotations.disallowed_by_default_enum'?: (boolean);
'.udpa.annotations.enum_value_migrate'?: (_udpa_annotations_MigrateAnnotation | null);
'.envoy.annotations.deprecated_at_minor_version_enum'?: (string);
}
export interface EnumValueOptions__Output {
'deprecated': (boolean);
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
'.envoy.annotations.disallowed_by_default_enum': (boolean);
'.udpa.annotations.enum_value_migrate': (_udpa_annotations_MigrateAnnotation__Output | null);
'.envoy.annotations.deprecated_at_minor_version_enum': (string);
}

View File

@ -1,9 +1,6 @@
// Original file: null
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
import type { FieldRules as _validate_FieldRules, FieldRules__Output as _validate_FieldRules__Output } from '../../validate/FieldRules';
import type { FieldMigrateAnnotation as _udpa_annotations_FieldMigrateAnnotation, FieldMigrateAnnotation__Output as _udpa_annotations_FieldMigrateAnnotation__Output } from '../../udpa/annotations/FieldMigrateAnnotation';
import type { FieldStatusAnnotation as _xds_annotations_v3_FieldStatusAnnotation, FieldStatusAnnotation__Output as _xds_annotations_v3_FieldStatusAnnotation__Output } from '../../xds/annotations/v3/FieldStatusAnnotation';
// Original file: null
@ -29,11 +26,6 @@ export interface FieldOptions {
'jstype'?: (_google_protobuf_FieldOptions_JSType | keyof typeof _google_protobuf_FieldOptions_JSType);
'weak'?: (boolean);
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
'.validate.rules'?: (_validate_FieldRules | null);
'.envoy.annotations.deprecated_at_minor_version'?: (string);
'.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation | null);
'.envoy.annotations.disallowed_by_default'?: (boolean);
'.xds.annotations.v3.field_status'?: (_xds_annotations_v3_FieldStatusAnnotation | null);
}
export interface FieldOptions__Output {
@ -44,9 +36,4 @@ export interface FieldOptions__Output {
'jstype': (keyof typeof _google_protobuf_FieldOptions_JSType);
'weak': (boolean);
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
'.validate.rules': (_validate_FieldRules__Output | null);
'.envoy.annotations.deprecated_at_minor_version': (string);
'.udpa.annotations.field_migrate': (_udpa_annotations_FieldMigrateAnnotation__Output | null);
'.envoy.annotations.disallowed_by_default': (boolean);
'.xds.annotations.v3.field_status': (_xds_annotations_v3_FieldStatusAnnotation__Output | null);
}

View File

@ -1,9 +1,7 @@
// Original file: null
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
import type { FileMigrateAnnotation as _udpa_annotations_FileMigrateAnnotation, FileMigrateAnnotation__Output as _udpa_annotations_FileMigrateAnnotation__Output } from '../../udpa/annotations/FileMigrateAnnotation';
import type { StatusAnnotation as _udpa_annotations_StatusAnnotation, StatusAnnotation__Output as _udpa_annotations_StatusAnnotation__Output } from '../../udpa/annotations/StatusAnnotation';
import type { FileStatusAnnotation as _xds_annotations_v3_FileStatusAnnotation, FileStatusAnnotation__Output as _xds_annotations_v3_FileStatusAnnotation__Output } from '../../xds/annotations/v3/FileStatusAnnotation';
// Original file: null
@ -29,9 +27,7 @@ export interface FileOptions {
'objcClassPrefix'?: (string);
'csharpNamespace'?: (string);
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
'.udpa.annotations.file_migrate'?: (_udpa_annotations_FileMigrateAnnotation | null);
'.udpa.annotations.file_status'?: (_udpa_annotations_StatusAnnotation | null);
'.xds.annotations.v3.file_status'?: (_xds_annotations_v3_FileStatusAnnotation | null);
}
export interface FileOptions__Output {
@ -50,7 +46,5 @@ export interface FileOptions__Output {
'objcClassPrefix': (string);
'csharpNamespace': (string);
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
'.udpa.annotations.file_migrate': (_udpa_annotations_FileMigrateAnnotation__Output | null);
'.udpa.annotations.file_status': (_udpa_annotations_StatusAnnotation__Output | null);
'.xds.annotations.v3.file_status': (_xds_annotations_v3_FileStatusAnnotation__Output | null);
}

View File

@ -1,9 +1,6 @@
// Original file: null
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
import type { VersioningAnnotation as _udpa_annotations_VersioningAnnotation, VersioningAnnotation__Output as _udpa_annotations_VersioningAnnotation__Output } from '../../udpa/annotations/VersioningAnnotation';
import type { MigrateAnnotation as _udpa_annotations_MigrateAnnotation, MigrateAnnotation__Output as _udpa_annotations_MigrateAnnotation__Output } from '../../udpa/annotations/MigrateAnnotation';
import type { MessageStatusAnnotation as _xds_annotations_v3_MessageStatusAnnotation, MessageStatusAnnotation__Output as _xds_annotations_v3_MessageStatusAnnotation__Output } from '../../xds/annotations/v3/MessageStatusAnnotation';
export interface MessageOptions {
'messageSetWireFormat'?: (boolean);
@ -11,10 +8,6 @@ export interface MessageOptions {
'deprecated'?: (boolean);
'mapEntry'?: (boolean);
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
'.validate.disabled'?: (boolean);
'.udpa.annotations.versioning'?: (_udpa_annotations_VersioningAnnotation | null);
'.udpa.annotations.message_migrate'?: (_udpa_annotations_MigrateAnnotation | null);
'.xds.annotations.v3.message_status'?: (_xds_annotations_v3_MessageStatusAnnotation | null);
}
export interface MessageOptions__Output {
@ -23,8 +16,4 @@ export interface MessageOptions__Output {
'deprecated': (boolean);
'mapEntry': (boolean);
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
'.validate.disabled': (boolean);
'.udpa.annotations.versioning': (_udpa_annotations_VersioningAnnotation__Output | null);
'.udpa.annotations.message_migrate': (_udpa_annotations_MigrateAnnotation__Output | null);
'.xds.annotations.v3.message_status': (_xds_annotations_v3_MessageStatusAnnotation__Output | null);
}

View File

@ -4,10 +4,8 @@ import type { UninterpretedOption as _google_protobuf_UninterpretedOption, Unint
export interface OneofOptions {
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
'.validate.required'?: (boolean);
}
export interface OneofOptions__Output {
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
'.validate.required': (boolean);
}

View File

@ -0,0 +1,52 @@
import type * as grpc from '@grpc/grpc-js';
import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader';
type SubtypeConstructor<Constructor extends new (...args: any) => any, Subtype> = {
new(...args: ConstructorParameters<Constructor>): Subtype;
};
export interface ProtoGrpcType {
envoy: {
extensions: {
load_balancing_policies: {
pick_first: {
v3: {
PickFirst: MessageTypeDefinition
}
}
}
}
}
google: {
protobuf: {
DescriptorProto: MessageTypeDefinition
EnumDescriptorProto: MessageTypeDefinition
EnumOptions: MessageTypeDefinition
EnumValueDescriptorProto: MessageTypeDefinition
EnumValueOptions: MessageTypeDefinition
FieldDescriptorProto: MessageTypeDefinition
FieldOptions: MessageTypeDefinition
FileDescriptorProto: MessageTypeDefinition
FileDescriptorSet: MessageTypeDefinition
FileOptions: MessageTypeDefinition
GeneratedCodeInfo: MessageTypeDefinition
MessageOptions: MessageTypeDefinition
MethodDescriptorProto: MessageTypeDefinition
MethodOptions: MessageTypeDefinition
OneofDescriptorProto: MessageTypeDefinition
OneofOptions: MessageTypeDefinition
ServiceDescriptorProto: MessageTypeDefinition
ServiceOptions: MessageTypeDefinition
SourceCodeInfo: MessageTypeDefinition
UninterpretedOption: MessageTypeDefinition
}
}
udpa: {
annotations: {
PackageVersionStatus: EnumTypeDefinition
StatusAnnotation: MessageTypeDefinition
}
}
}

View File

@ -29,6 +29,7 @@ import * as fault_injection_filter from './http-filter/fault-injection-filter';
import * as csds from './csds';
import * as round_robin_lb from './lb-policy-registry/round-robin';
import * as typed_struct_lb from './lb-policy-registry/typed-struct';
import * as pick_first_lb from './lb-policy-registry/pick-first';
/**
* Register the "xds:" name scheme with the @grpc/grpc-js library.
@ -48,4 +49,5 @@ export function register() {
csds.setup();
round_robin_lb.setup();
typed_struct_lb.setup();
pick_first_lb.setup();
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2023 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// https://github.com/grpc/proposal/blob/master/A62-pick-first.md#pick_first-via-xds-1
import { LoadBalancingConfig } from "@grpc/grpc-js";
import { LoadBalancingPolicy__Output } from "../generated/envoy/config/cluster/v3/LoadBalancingPolicy";
import { TypedExtensionConfig__Output } from "../generated/envoy/config/core/v3/TypedExtensionConfig";
import { loadProtosWithOptionsSync } from "@grpc/proto-loader/build/src/util";
import { Any__Output } from "../generated/google/protobuf/Any";
import { PickFirst__Output } from "../generated/envoy/extensions/load_balancing_policies/pick_first/v3/PickFirst";
import { EXPERIMENTAL_PICK_FIRST } from "../environment";
import { registerLbPolicy } from "../lb-policy-registry";
const PICK_FIRST_TYPE_URL = 'type.googleapis.com/envoy.extensions.load_balancing_policies.pick_first.v3.PickFirst';
const resourceRoot = loadProtosWithOptionsSync([
'envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto'], {
keepCase: true,
includeDirs: [
// Paths are relative to src/build/lb-policy-registry
__dirname + '/../../../deps/envoy-api/',
__dirname + '/../../../deps/xds/',
__dirname + '/../../../deps/protoc-gen-validate'
],
}
);
const toObjectOptions = {
longs: String,
enums: String,
defaults: true,
oneofs: true
}
function decodePickFirstConfig(message: Any__Output): PickFirst__Output {
const name = message.type_url.substring(message.type_url.lastIndexOf('/') + 1);
const type = resourceRoot.lookup(name);
if (type) {
const decodedMessage = (type as any).decode(message.value);
return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as PickFirst__Output;
} else {
throw new Error(`TypedStruct parsing error: unexpected type URL ${message.type_url}`);
}
}
function convertToLoadBalancingPolicy(protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig | null {
if (protoPolicy.typed_config?.type_url !== PICK_FIRST_TYPE_URL) {
throw new Error(`Pick first LB policy parsing error: unexpected type URL ${protoPolicy.typed_config?.type_url}`);
}
const pickFirstMessage = decodePickFirstConfig(protoPolicy.typed_config);
return {
pick_first: {
shuffleAddressList: pickFirstMessage.shuffle_address_list
}
};
}
export function setup() {
if (EXPERIMENTAL_PICK_FIRST) {
registerLbPolicy(PICK_FIRST_TYPE_URL, convertToLoadBalancingPolicy);
}
}

View File

@ -38,6 +38,7 @@ import PickResultType = experimental.PickResultType;
import createChildChannelControlHelper = experimental.createChildChannelControlHelper;
import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig;
import registerLoadBalancerType = experimental.registerLoadBalancerType;
import { PickFirst } from "../src/generated/envoy/extensions/load_balancing_policies/pick_first/v3/PickFirst";
const LB_POLICY_NAME = 'test.RpcBehaviorLoadBalancer';
@ -297,5 +298,28 @@ describe('Custom LB policies', () => {
done();
});
}, reason => done(reason));
})
});
it('Should handle pick_first', done => {
const lbPolicy: PickFirst & AnyExtension = {
'@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.pick_first.v3.PickFirst',
shuffle_address_list: true
};
const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}], lbPolicy);
const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]);
routeGroup.startAllBackends().then(() => {
xdsServer.setEdsResource(cluster.getEndpointConfig());
xdsServer.setCdsResource(cluster.getClusterConfig());
xdsServer.setRdsResource(routeGroup.getRouteConfiguration());
xdsServer.setLdsResource(routeGroup.getListener());
xdsServer.addResponseListener((typeUrl, responseState) => {
if (responseState.state === 'NACKED') {
client.stopCalls();
assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`);
}
})
client = XdsTestClient.createFromServer('listener1', xdsServer);
client.sendOneCall(done);
}, reason => done(reason));
});
});

View File

@ -45,6 +45,7 @@ const loadedProtos = loadPackageDefinition(loadSync(
'envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.proto',
'envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto',
'envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto',
'envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto',
'xds/type/v3/typed_struct.proto'
],
{