From 6567f8d7cd908bf259d7e93bfc2ee06cdfe015fe Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 13 Sep 2023 14:07:22 -0700 Subject: [PATCH 1/2] Update code generation with PickFirst message --- packages/grpc-js-xds/package.json | 2 +- .../pick_first/v3/PickFirst.ts | 26 ++++++++++ .../generated/google/protobuf/EnumOptions.ts | 3 -- .../google/protobuf/EnumValueOptions.ts | 7 --- .../generated/google/protobuf/FieldOptions.ts | 13 ----- .../generated/google/protobuf/FileOptions.ts | 6 --- .../google/protobuf/MessageOptions.ts | 11 ---- .../generated/google/protobuf/OneofOptions.ts | 2 - .../grpc-js-xds/src/generated/pick_first.ts | 52 +++++++++++++++++++ 9 files changed, 79 insertions(+), 43 deletions(-) create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/pick_first/v3/PickFirst.ts create mode 100644 packages/grpc-js-xds/src/generated/pick_first.ts diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 6ec4548d5..389f2b731 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -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" }, diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/pick_first/v3/PickFirst.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/pick_first/v3/PickFirst.ts new file mode 100644 index 000000000..1208575d0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/pick_first/v3/PickFirst.ts @@ -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); +} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/EnumOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/EnumOptions.ts index 777901a54..b92ade4f9 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/EnumOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumOptions.ts @@ -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); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts index 9ba51ed60..e60ee6f4c 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts @@ -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); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts index f59acfbfe..3c3b446c9 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts @@ -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); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts index 48a376cd0..84500fc30 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts @@ -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); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts index 71d8c855b..31f669eb0 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts @@ -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); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/OneofOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/OneofOptions.ts index b54ecb0b1..d81d34797 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/OneofOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/OneofOptions.ts @@ -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); } diff --git a/packages/grpc-js-xds/src/generated/pick_first.ts b/packages/grpc-js-xds/src/generated/pick_first.ts new file mode 100644 index 000000000..9bf20e03f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/pick_first.ts @@ -0,0 +1,52 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): 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 + } + } +} + From fe74b60440d124adfd38afa6338117501fb17c13 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 13 Sep 2023 14:27:25 -0700 Subject: [PATCH 2/2] grpc-js-xds: Add support for pick_first in xDS config --- packages/grpc-js-xds/gulpfile.ts | 1 + packages/grpc-js-xds/src/environment.ts | 1 + packages/grpc-js-xds/src/index.ts | 2 + .../src/lb-policy-registry/pick-first.ts | 77 +++++++++++++++++++ .../test/test-custom-lb-policies.ts | 26 ++++++- packages/grpc-js-xds/test/xds-server.ts | 1 + 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 packages/grpc-js-xds/src/lb-policy-registry/pick-first.ts diff --git a/packages/grpc-js-xds/gulpfile.ts b/packages/grpc-js-xds/gulpfile.ts index 93f93d8fc..becf109a6 100644 --- a/packages/grpc-js-xds/gulpfile.ts +++ b/packages/grpc-js-xds/gulpfile.ts @@ -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'; } diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index 858903111..02f14dabc 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -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'; diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts index 70aa5bef2..aa603c9b7 100644 --- a/packages/grpc-js-xds/src/index.ts +++ b/packages/grpc-js-xds/src/index.ts @@ -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(); } diff --git a/packages/grpc-js-xds/src/lb-policy-registry/pick-first.ts b/packages/grpc-js-xds/src/lb-policy-registry/pick-first.ts new file mode 100644 index 000000000..bfe2793d8 --- /dev/null +++ b/packages/grpc-js-xds/src/lb-policy-registry/pick-first.ts @@ -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); + } +} diff --git a/packages/grpc-js-xds/test/test-custom-lb-policies.ts b/packages/grpc-js-xds/test/test-custom-lb-policies.ts index 6da6fecc8..443601e36 100644 --- a/packages/grpc-js-xds/test/test-custom-lb-policies.ts +++ b/packages/grpc-js-xds/test/test-custom-lb-policies.ts @@ -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)); + }); + }); diff --git a/packages/grpc-js-xds/test/xds-server.ts b/packages/grpc-js-xds/test/xds-server.ts index 2aa62bdd8..d8500c836 100644 --- a/packages/grpc-js-xds/test/xds-server.ts +++ b/packages/grpc-js-xds/test/xds-server.ts @@ -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' ], {