Merge pull request #1522 from murgatroid99/grpc-js_xds_resolver

grpc-js: Add XdsResolver and corresponding XdsClient behavior
This commit is contained in:
Michael Lumish 2020-08-06 10:00:07 -07:00 committed by GitHub
commit 2388e31f8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 2488 additions and 172 deletions

View File

@ -48,7 +48,7 @@
"clean": "node -e 'require(\"rimraf\")(\"./build\", () => {})'",
"compile": "tsc -p .",
"format": "clang-format -i -style=\"{Language: JavaScript, BasedOnStyle: Google, ColumnLimit: 80}\" src/*.ts test/*.ts",
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs deps/envoy-api/ deps/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib ../index envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto envoy/api/v2/listener.proto envoy/api/v2/route.proto envoy/api/v2/cluster.proto envoy/api/v2/endpoint.proto",
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs deps/envoy-api/ deps/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib ../index envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto envoy/api/v2/listener.proto envoy/api/v2/route.proto envoy/api/v2/cluster.proto envoy/api/v2/endpoint.proto envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto",
"lint": "npm run check",
"prepare": "npm run compile",
"test": "gulp test",

View File

@ -34,7 +34,6 @@ import { CallCredentialsFilterFactory } from './call-credentials-filter';
import { DeadlineFilterFactory } from './deadline-filter';
import { CompressionFilterFactory } from './compression-filter';
import { getDefaultAuthority, mapUriDefaultScheme } from './resolver';
import { ServiceConfig, validateServiceConfig } from './service-config';
import { trace, log } from './logging';
import { SubchannelAddress } from './subchannel';
import { MaxMessageSizeFilterFactory } from './max-message-size-filter';
@ -220,20 +219,10 @@ export class ChannelImplementation implements Channel {
);
},
};
// TODO(murgatroid99): check channel arg for default service config
let defaultServiceConfig: ServiceConfig = {
loadBalancingConfig: [],
methodConfig: [],
};
if (options['grpc.service_config']) {
defaultServiceConfig = validateServiceConfig(
JSON.parse(options['grpc.service_config']!)
);
}
this.resolvingLoadBalancer = new ResolvingLoadBalancer(
this.target,
channelControlHelper,
defaultServiceConfig
options
);
this.filterStackFactory = new FilterStackFactory([
new CallCredentialsFilterFactory(this),

View File

@ -0,0 +1,204 @@
// Original file: deps/envoy-api/envoy/api/v2/scoped_route.proto
export interface _envoy_api_v2_ScopedRouteConfiguration_Key_Fragment {
/**
* A string to match against.
*/
'string_key'?: (string);
'type'?: "string_key";
}
export interface _envoy_api_v2_ScopedRouteConfiguration_Key_Fragment__Output {
/**
* A string to match against.
*/
'string_key'?: (string);
'type': "string_key";
}
/**
* Specifies a key which is matched against the output of the
* :ref:`scope_key_builder<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scope_key_builder>`
* specified in the HttpConnectionManager. The matching is done per HTTP
* request and is dependent on the order of the fragments contained in the
* Key.
*/
export interface _envoy_api_v2_ScopedRouteConfiguration_Key {
/**
* The ordered set of fragments to match against. The order must match the
* fragments in the corresponding
* :ref:`scope_key_builder<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scope_key_builder>`.
*/
'fragments'?: (_envoy_api_v2_ScopedRouteConfiguration_Key_Fragment)[];
}
/**
* Specifies a key which is matched against the output of the
* :ref:`scope_key_builder<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scope_key_builder>`
* specified in the HttpConnectionManager. The matching is done per HTTP
* request and is dependent on the order of the fragments contained in the
* Key.
*/
export interface _envoy_api_v2_ScopedRouteConfiguration_Key__Output {
/**
* The ordered set of fragments to match against. The order must match the
* fragments in the corresponding
* :ref:`scope_key_builder<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scope_key_builder>`.
*/
'fragments': (_envoy_api_v2_ScopedRouteConfiguration_Key_Fragment__Output)[];
}
/**
* Specifies a routing scope, which associates a
* :ref:`Key<envoy_api_msg_ScopedRouteConfiguration.Key>` to a
* :ref:`envoy_api_msg_RouteConfiguration` (identified by its resource name).
*
* The HTTP connection manager builds up a table consisting of these Key to
* RouteConfiguration mappings, and looks up the RouteConfiguration to use per
* request according to the algorithm specified in the
* :ref:`scope_key_builder<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scope_key_builder>`
* assigned to the HttpConnectionManager.
*
* For example, with the following configurations (in YAML):
*
* HttpConnectionManager config:
*
* .. code::
*
* ...
* scoped_routes:
* name: foo-scoped-routes
* scope_key_builder:
* fragments:
* - header_value_extractor:
* name: X-Route-Selector
* element_separator: ,
* element:
* separator: =
* key: vip
*
* ScopedRouteConfiguration resources (specified statically via
* :ref:`scoped_route_configurations_list<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scoped_route_configurations_list>`
* or obtained dynamically via SRDS):
*
* .. code::
*
* (1)
* name: route-scope1
* route_configuration_name: route-config1
* key:
* fragments:
* - string_key: 172.10.10.20
*
* (2)
* name: route-scope2
* route_configuration_name: route-config2
* key:
* fragments:
* - string_key: 172.20.20.30
*
* A request from a client such as:
*
* .. code::
*
* GET / HTTP/1.1
* Host: foo.com
* X-Route-Selector: vip=172.10.10.20
*
* would result in the routing table defined by the `route-config1`
* RouteConfiguration being assigned to the HTTP request/stream.
*/
export interface ScopedRouteConfiguration {
/**
* The name assigned to the routing scope.
*/
'name'?: (string);
/**
* The resource name to use for a :ref:`envoy_api_msg_DiscoveryRequest` to an
* RDS server to fetch the :ref:`envoy_api_msg_RouteConfiguration` associated
* with this scope.
*/
'route_configuration_name'?: (string);
/**
* The key to match against.
*/
'key'?: (_envoy_api_v2_ScopedRouteConfiguration_Key);
}
/**
* Specifies a routing scope, which associates a
* :ref:`Key<envoy_api_msg_ScopedRouteConfiguration.Key>` to a
* :ref:`envoy_api_msg_RouteConfiguration` (identified by its resource name).
*
* The HTTP connection manager builds up a table consisting of these Key to
* RouteConfiguration mappings, and looks up the RouteConfiguration to use per
* request according to the algorithm specified in the
* :ref:`scope_key_builder<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scope_key_builder>`
* assigned to the HttpConnectionManager.
*
* For example, with the following configurations (in YAML):
*
* HttpConnectionManager config:
*
* .. code::
*
* ...
* scoped_routes:
* name: foo-scoped-routes
* scope_key_builder:
* fragments:
* - header_value_extractor:
* name: X-Route-Selector
* element_separator: ,
* element:
* separator: =
* key: vip
*
* ScopedRouteConfiguration resources (specified statically via
* :ref:`scoped_route_configurations_list<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scoped_route_configurations_list>`
* or obtained dynamically via SRDS):
*
* .. code::
*
* (1)
* name: route-scope1
* route_configuration_name: route-config1
* key:
* fragments:
* - string_key: 172.10.10.20
*
* (2)
* name: route-scope2
* route_configuration_name: route-config2
* key:
* fragments:
* - string_key: 172.20.20.30
*
* A request from a client such as:
*
* .. code::
*
* GET / HTTP/1.1
* Host: foo.com
* X-Route-Selector: vip=172.10.10.20
*
* would result in the routing table defined by the `route-config1`
* RouteConfiguration being assigned to the HTTP request/stream.
*/
export interface ScopedRouteConfiguration__Output {
/**
* The name assigned to the routing scope.
*/
'name': (string);
/**
* The resource name to use for a :ref:`envoy_api_msg_DiscoveryRequest` to an
* RDS server to fetch the :ref:`envoy_api_msg_RouteConfiguration` associated
* with this scope.
*/
'route_configuration_name': (string);
/**
* The key to match against.
*/
'key'?: (_envoy_api_v2_ScopedRouteConfiguration_Key__Output);
}

View File

@ -0,0 +1,34 @@
// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto
import { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../../../google/protobuf/Struct';
import { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../../google/protobuf/Any';
export interface HttpFilter {
/**
* The name of the filter to instantiate. The name must match a
* :ref:`supported filter <config_http_filters>`.
*/
'name'?: (string);
'config'?: (_google_protobuf_Struct);
'typed_config'?: (_google_protobuf_Any);
/**
* Filter specific configuration which depends on the filter being instantiated. See the supported
* filters for further documentation.
*/
'config_type'?: "config"|"typed_config";
}
export interface HttpFilter__Output {
/**
* The name of the filter to instantiate. The name must match a
* :ref:`supported filter <config_http_filters>`.
*/
'name': (string);
'config'?: (_google_protobuf_Struct__Output);
'typed_config'?: (_google_protobuf_Any__Output);
/**
* Filter specific configuration which depends on the filter being instantiated. See the supported
* filters for further documentation.
*/
'config_type': "config"|"typed_config";
}

View File

@ -0,0 +1,31 @@
// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto
import { ConfigSource as _envoy_api_v2_core_ConfigSource, ConfigSource__Output as _envoy_api_v2_core_ConfigSource__Output } from '../../../../../../envoy/api/v2/core/ConfigSource';
export interface Rds {
/**
* Configuration source specifier for RDS.
*/
'config_source'?: (_envoy_api_v2_core_ConfigSource);
/**
* The name of the route configuration. This name will be passed to the RDS
* API. This allows an Envoy configuration with multiple HTTP listeners (and
* associated HTTP connection manager filters) to use different route
* configurations.
*/
'route_config_name'?: (string);
}
export interface Rds__Output {
/**
* Configuration source specifier for RDS.
*/
'config_source'?: (_envoy_api_v2_core_ConfigSource__Output);
/**
* The name of the route configuration. This name will be passed to the RDS
* API. This allows an Envoy configuration with multiple HTTP listeners (and
* associated HTTP connection manager filters) to use different route
* configurations.
*/
'route_config_name': (string);
}

View File

@ -0,0 +1,17 @@
// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto
import { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../../google/protobuf/Any';
export interface RequestIDExtension {
/**
* Request ID extension specific configuration.
*/
'typed_config'?: (_google_protobuf_Any);
}
export interface RequestIDExtension__Output {
/**
* Request ID extension specific configuration.
*/
'typed_config'?: (_google_protobuf_Any__Output);
}

View File

@ -0,0 +1,17 @@
// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto
import { ConfigSource as _envoy_api_v2_core_ConfigSource, ConfigSource__Output as _envoy_api_v2_core_ConfigSource__Output } from '../../../../../../envoy/api/v2/core/ConfigSource';
export interface ScopedRds {
/**
* Configuration source specifier for scoped RDS.
*/
'scoped_rds_config_source'?: (_envoy_api_v2_core_ConfigSource);
}
export interface ScopedRds__Output {
/**
* Configuration source specifier for scoped RDS.
*/
'scoped_rds_config_source'?: (_envoy_api_v2_core_ConfigSource__Output);
}

View File

@ -0,0 +1,17 @@
// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto
import { ScopedRouteConfiguration as _envoy_api_v2_ScopedRouteConfiguration, ScopedRouteConfiguration__Output as _envoy_api_v2_ScopedRouteConfiguration__Output } from '../../../../../../envoy/api/v2/ScopedRouteConfiguration';
/**
* This message is used to work around the limitations with 'oneof' and repeated fields.
*/
export interface ScopedRouteConfigurationsList {
'scoped_route_configurations'?: (_envoy_api_v2_ScopedRouteConfiguration)[];
}
/**
* This message is used to work around the limitations with 'oneof' and repeated fields.
*/
export interface ScopedRouteConfigurationsList__Output {
'scoped_route_configurations': (_envoy_api_v2_ScopedRouteConfiguration__Output)[];
}

View File

@ -0,0 +1,265 @@
// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto
import { ConfigSource as _envoy_api_v2_core_ConfigSource, ConfigSource__Output as _envoy_api_v2_core_ConfigSource__Output } from '../../../../../../envoy/api/v2/core/ConfigSource';
import { ScopedRouteConfigurationsList as _envoy_config_filter_network_http_connection_manager_v2_ScopedRouteConfigurationsList, ScopedRouteConfigurationsList__Output as _envoy_config_filter_network_http_connection_manager_v2_ScopedRouteConfigurationsList__Output } from '../../../../../../envoy/config/filter/network/http_connection_manager/v2/ScopedRouteConfigurationsList';
import { ScopedRds as _envoy_config_filter_network_http_connection_manager_v2_ScopedRds, ScopedRds__Output as _envoy_config_filter_network_http_connection_manager_v2_ScopedRds__Output } from '../../../../../../envoy/config/filter/network/http_connection_manager/v2/ScopedRds';
/**
* Specifies the mechanism for constructing key fragments which are composed into scope keys.
*/
export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder {
/**
* Specifies how a header field's value should be extracted.
*/
'header_value_extractor'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor);
'type'?: "header_value_extractor";
}
/**
* Specifies the mechanism for constructing key fragments which are composed into scope keys.
*/
export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder__Output {
/**
* Specifies how a header field's value should be extracted.
*/
'header_value_extractor'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor__Output);
'type': "header_value_extractor";
}
/**
* Specifies how the value of a header should be extracted.
* The following example maps the structure of a header to the fields in this message.
*
* .. code::
*
* <0> <1> <-- index
* X-Header: a=b;c=d
* | || |
* | || \----> <element_separator>
* | ||
* | |\----> <element.separator>
* | |
* | \----> <element.key>
* |
* \----> <name>
*
* Each 'a=b' key-value pair constitutes an 'element' of the header field.
*/
export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor {
/**
* The name of the header field to extract the value from.
*/
'name'?: (string);
/**
* The element separator (e.g., ';' separates 'a;b;c;d').
* Default: empty string. This causes the entirety of the header field to be extracted.
* If this field is set to an empty string and 'index' is used in the oneof below, 'index'
* must be set to 0.
*/
'element_separator'?: (string);
/**
* Specifies the zero based index of the element to extract.
* Note Envoy concatenates multiple values of the same header key into a comma separated
* string, the splitting always happens after the concatenation.
*/
'index'?: (number);
/**
* Specifies the key value pair to extract the value from.
*/
'element'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement);
'extract_type'?: "index"|"element";
}
/**
* Specifies how the value of a header should be extracted.
* The following example maps the structure of a header to the fields in this message.
*
* .. code::
*
* <0> <1> <-- index
* X-Header: a=b;c=d
* | || |
* | || \----> <element_separator>
* | ||
* | |\----> <element.separator>
* | |
* | \----> <element.key>
* |
* \----> <name>
*
* Each 'a=b' key-value pair constitutes an 'element' of the header field.
*/
export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor__Output {
/**
* The name of the header field to extract the value from.
*/
'name': (string);
/**
* The element separator (e.g., ';' separates 'a;b;c;d').
* Default: empty string. This causes the entirety of the header field to be extracted.
* If this field is set to an empty string and 'index' is used in the oneof below, 'index'
* must be set to 0.
*/
'element_separator': (string);
/**
* Specifies the zero based index of the element to extract.
* Note Envoy concatenates multiple values of the same header key into a comma separated
* string, the splitting always happens after the concatenation.
*/
'index'?: (number);
/**
* Specifies the key value pair to extract the value from.
*/
'element'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement__Output);
'extract_type': "index"|"element";
}
/**
* Specifies a header field's key value pair to match on.
*/
export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement {
/**
* The separator between key and value (e.g., '=' separates 'k=v;...').
* If an element is an empty string, the element is ignored.
* If an element contains no separator, the whole element is parsed as key and the
* fragment value is an empty string.
* If there are multiple values for a matched key, the first value is returned.
*/
'separator'?: (string);
/**
* The key to match on.
*/
'key'?: (string);
}
/**
* Specifies a header field's key value pair to match on.
*/
export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement__Output {
/**
* The separator between key and value (e.g., '=' separates 'k=v;...').
* If an element is an empty string, the element is ignored.
* If an element contains no separator, the whole element is parsed as key and the
* fragment value is an empty string.
* If there are multiple values for a matched key, the first value is returned.
*/
'separator': (string);
/**
* The key to match on.
*/
'key': (string);
}
/**
* Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These
* keys are matched against a set of :ref:`Key<envoy_api_msg_ScopedRouteConfiguration.Key>`
* objects assembled from :ref:`ScopedRouteConfiguration<envoy_api_msg_ScopedRouteConfiguration>`
* messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via
* :ref:`scoped_route_configurations_list<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scoped_route_configurations_list>`.
*
* Upon receiving a request's headers, the Router will build a key using the algorithm specified
* by this message. This key will be used to look up the routing table (i.e., the
* :ref:`RouteConfiguration<envoy_api_msg_RouteConfiguration>`) to use for the request.
*/
export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder {
/**
* The final(built) scope key consists of the ordered union of these fragments, which are compared in order with the
* fragments of a :ref:`ScopedRouteConfiguration<envoy_api_msg_ScopedRouteConfiguration>`.
* A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key.
*/
'fragments'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder)[];
}
/**
* Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These
* keys are matched against a set of :ref:`Key<envoy_api_msg_ScopedRouteConfiguration.Key>`
* objects assembled from :ref:`ScopedRouteConfiguration<envoy_api_msg_ScopedRouteConfiguration>`
* messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via
* :ref:`scoped_route_configurations_list<envoy_api_field_config.filter.network.http_connection_manager.v2.ScopedRoutes.scoped_route_configurations_list>`.
*
* Upon receiving a request's headers, the Router will build a key using the algorithm specified
* by this message. This key will be used to look up the routing table (i.e., the
* :ref:`RouteConfiguration<envoy_api_msg_RouteConfiguration>`) to use for the request.
*/
export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder__Output {
/**
* The final(built) scope key consists of the ordered union of these fragments, which are compared in order with the
* fragments of a :ref:`ScopedRouteConfiguration<envoy_api_msg_ScopedRouteConfiguration>`.
* A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key.
*/
'fragments': (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder__Output)[];
}
/**
* [#next-free-field: 6]
*/
export interface ScopedRoutes {
/**
* The name assigned to the scoped routing configuration.
*/
'name'?: (string);
/**
* The algorithm to use for constructing a scope key for each request.
*/
'scope_key_builder'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder);
/**
* Configuration source specifier for RDS.
* This config source is used to subscribe to RouteConfiguration resources specified in
* ScopedRouteConfiguration messages.
*/
'rds_config_source'?: (_envoy_api_v2_core_ConfigSource);
/**
* The set of routing scopes corresponding to the HCM. A scope is assigned to a request by
* matching a key constructed from the request's attributes according to the algorithm specified
* by the
* :ref:`ScopeKeyBuilder<envoy_api_msg_config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder>`
* in this message.
*/
'scoped_route_configurations_list'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRouteConfigurationsList);
/**
* The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS
* API. A scope is assigned to a request by matching a key constructed from the request's
* attributes according to the algorithm specified by the
* :ref:`ScopeKeyBuilder<envoy_api_msg_config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder>`
* in this message.
*/
'scoped_rds'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRds);
'config_specifier'?: "scoped_route_configurations_list"|"scoped_rds";
}
/**
* [#next-free-field: 6]
*/
export interface ScopedRoutes__Output {
/**
* The name assigned to the scoped routing configuration.
*/
'name': (string);
/**
* The algorithm to use for constructing a scope key for each request.
*/
'scope_key_builder'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder__Output);
/**
* Configuration source specifier for RDS.
* This config source is used to subscribe to RouteConfiguration resources specified in
* ScopedRouteConfiguration messages.
*/
'rds_config_source'?: (_envoy_api_v2_core_ConfigSource__Output);
/**
* The set of routing scopes corresponding to the HCM. A scope is assigned to a request by
* matching a key constructed from the request's attributes according to the algorithm specified
* by the
* :ref:`ScopeKeyBuilder<envoy_api_msg_config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder>`
* in this message.
*/
'scoped_route_configurations_list'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRouteConfigurationsList__Output);
/**
* The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS
* API. A scope is assigned to a request by matching a key constructed from the request's
* attributes according to the algorithm specified by the
* :ref:`ScopeKeyBuilder<envoy_api_msg_config.filter.network.http_connection_manager.v2.ScopedRoutes.ScopeKeyBuilder>`
* in this message.
*/
'scoped_rds'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRds__Output);
'config_specifier': "scoped_route_configurations_list"|"scoped_rds";
}

View File

@ -0,0 +1,114 @@
// Original file: deps/envoy-api/envoy/config/trace/v2/http_tracer.proto
import { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct';
import { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any';
/**
* Configuration for an HTTP tracer provider used by Envoy.
*
* The configuration is defined by the
* :ref:`HttpConnectionManager.Tracing <envoy_api_msg_config.filter.network.http_connection_manager.v2.HttpConnectionManager.Tracing>`
* :ref:`provider <envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.Tracing.provider>`
* field.
*/
export interface _envoy_config_trace_v2_Tracing_Http {
/**
* The name of the HTTP trace driver to instantiate. The name must match a
* supported HTTP trace driver. Built-in trace drivers:
*
* - *envoy.tracers.lightstep*
* - *envoy.tracers.zipkin*
* - *envoy.tracers.dynamic_ot*
* - *envoy.tracers.datadog*
* - *envoy.tracers.opencensus*
* - *envoy.tracers.xray*
*/
'name'?: (string);
'config'?: (_google_protobuf_Struct);
'typed_config'?: (_google_protobuf_Any);
/**
* Trace driver specific configuration which depends on the driver being instantiated.
* See the trace drivers for examples:
*
* - :ref:`LightstepConfig <envoy_api_msg_config.trace.v2.LightstepConfig>`
* - :ref:`ZipkinConfig <envoy_api_msg_config.trace.v2.ZipkinConfig>`
* - :ref:`DynamicOtConfig <envoy_api_msg_config.trace.v2.DynamicOtConfig>`
* - :ref:`DatadogConfig <envoy_api_msg_config.trace.v2.DatadogConfig>`
* - :ref:`OpenCensusConfig <envoy_api_msg_config.trace.v2.OpenCensusConfig>`
* - :ref:`AWS X-Ray <envoy_api_msg_config.trace.v2alpha.XRayConfig>`
*/
'config_type'?: "config"|"typed_config";
}
/**
* Configuration for an HTTP tracer provider used by Envoy.
*
* The configuration is defined by the
* :ref:`HttpConnectionManager.Tracing <envoy_api_msg_config.filter.network.http_connection_manager.v2.HttpConnectionManager.Tracing>`
* :ref:`provider <envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.Tracing.provider>`
* field.
*/
export interface _envoy_config_trace_v2_Tracing_Http__Output {
/**
* The name of the HTTP trace driver to instantiate. The name must match a
* supported HTTP trace driver. Built-in trace drivers:
*
* - *envoy.tracers.lightstep*
* - *envoy.tracers.zipkin*
* - *envoy.tracers.dynamic_ot*
* - *envoy.tracers.datadog*
* - *envoy.tracers.opencensus*
* - *envoy.tracers.xray*
*/
'name': (string);
'config'?: (_google_protobuf_Struct__Output);
'typed_config'?: (_google_protobuf_Any__Output);
/**
* Trace driver specific configuration which depends on the driver being instantiated.
* See the trace drivers for examples:
*
* - :ref:`LightstepConfig <envoy_api_msg_config.trace.v2.LightstepConfig>`
* - :ref:`ZipkinConfig <envoy_api_msg_config.trace.v2.ZipkinConfig>`
* - :ref:`DynamicOtConfig <envoy_api_msg_config.trace.v2.DynamicOtConfig>`
* - :ref:`DatadogConfig <envoy_api_msg_config.trace.v2.DatadogConfig>`
* - :ref:`OpenCensusConfig <envoy_api_msg_config.trace.v2.OpenCensusConfig>`
* - :ref:`AWS X-Ray <envoy_api_msg_config.trace.v2alpha.XRayConfig>`
*/
'config_type': "config"|"typed_config";
}
/**
* The tracing configuration specifies settings for an HTTP tracer provider used by Envoy.
*
* Envoy may support other tracers in the future, but right now the HTTP tracer is the only one
* supported.
*
* .. attention::
*
* Use of this message type has been deprecated in favor of direct use of
* :ref:`Tracing.Http <envoy_api_msg_config.trace.v2.Tracing.Http>`.
*/
export interface Tracing {
/**
* Provides configuration for the HTTP tracer.
*/
'http'?: (_envoy_config_trace_v2_Tracing_Http);
}
/**
* The tracing configuration specifies settings for an HTTP tracer provider used by Envoy.
*
* Envoy may support other tracers in the future, but right now the HTTP tracer is the only one
* supported.
*
* .. attention::
*
* Use of this message type has been deprecated in favor of direct use of
* :ref:`Tracing.Http <envoy_api_msg_config.trace.v2.Tracing.Http>`.
*/
export interface Tracing__Output {
/**
* Provides configuration for the HTTP tracer.
*/
'http'?: (_envoy_config_trace_v2_Tracing_Http__Output);
}

View File

@ -1,16 +1,13 @@
// Original file: null
import { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
import { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule';
export interface MethodOptions {
'deprecated'?: (boolean);
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
'.google.api.http'?: (_google_api_HttpRule);
}
export interface MethodOptions__Output {
'deprecated': (boolean);
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
'.google.api.http'?: (_google_api_HttpRule__Output);
}

View File

@ -0,0 +1,228 @@
import * as grpc from '../index';
import { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader';
type ConstructorArguments<Constructor> = Constructor extends new (...args: infer Args) => any ? Args: never;
type SubtypeConstructor<Constructor, Subtype> = {
new(...args: ConstructorArguments<Constructor>): Subtype;
}
export interface ProtoGrpcType {
envoy: {
annotations: {
}
api: {
v2: {
RouteConfiguration: MessageTypeDefinition
ScopedRouteConfiguration: MessageTypeDefinition
Vhds: MessageTypeDefinition
core: {
Address: MessageTypeDefinition
AggregatedConfigSource: MessageTypeDefinition
ApiConfigSource: MessageTypeDefinition
ApiVersion: EnumTypeDefinition
AsyncDataSource: MessageTypeDefinition
BackoffStrategy: MessageTypeDefinition
BindConfig: MessageTypeDefinition
BuildVersion: MessageTypeDefinition
CidrRange: MessageTypeDefinition
ConfigSource: MessageTypeDefinition
ControlPlane: MessageTypeDefinition
DataSource: MessageTypeDefinition
Extension: MessageTypeDefinition
GrpcProtocolOptions: MessageTypeDefinition
GrpcService: MessageTypeDefinition
HeaderMap: MessageTypeDefinition
HeaderValue: MessageTypeDefinition
HeaderValueOption: MessageTypeDefinition
Http1ProtocolOptions: MessageTypeDefinition
Http2ProtocolOptions: MessageTypeDefinition
HttpProtocolOptions: MessageTypeDefinition
HttpUri: MessageTypeDefinition
Locality: MessageTypeDefinition
Metadata: MessageTypeDefinition
Node: MessageTypeDefinition
Pipe: MessageTypeDefinition
RateLimitSettings: MessageTypeDefinition
RemoteDataSource: MessageTypeDefinition
RequestMethod: EnumTypeDefinition
RetryPolicy: MessageTypeDefinition
RoutingPriority: EnumTypeDefinition
RuntimeDouble: MessageTypeDefinition
RuntimeFeatureFlag: MessageTypeDefinition
RuntimeFractionalPercent: MessageTypeDefinition
RuntimeUInt32: MessageTypeDefinition
SelfConfigSource: MessageTypeDefinition
SocketAddress: MessageTypeDefinition
SocketOption: MessageTypeDefinition
TcpKeepalive: MessageTypeDefinition
TcpProtocolOptions: MessageTypeDefinition
TrafficDirection: EnumTypeDefinition
TransportSocket: MessageTypeDefinition
UpstreamHttpProtocolOptions: MessageTypeDefinition
}
route: {
CorsPolicy: MessageTypeDefinition
Decorator: MessageTypeDefinition
DirectResponseAction: MessageTypeDefinition
FilterAction: MessageTypeDefinition
HeaderMatcher: MessageTypeDefinition
HedgePolicy: MessageTypeDefinition
QueryParameterMatcher: MessageTypeDefinition
RateLimit: MessageTypeDefinition
RedirectAction: MessageTypeDefinition
RetryPolicy: MessageTypeDefinition
Route: MessageTypeDefinition
RouteAction: MessageTypeDefinition
RouteMatch: MessageTypeDefinition
Tracing: MessageTypeDefinition
VirtualCluster: MessageTypeDefinition
VirtualHost: MessageTypeDefinition
WeightedCluster: MessageTypeDefinition
}
}
}
config: {
filter: {
accesslog: {
v2: {
AccessLog: MessageTypeDefinition
AccessLogFilter: MessageTypeDefinition
AndFilter: MessageTypeDefinition
ComparisonFilter: MessageTypeDefinition
DurationFilter: MessageTypeDefinition
ExtensionFilter: MessageTypeDefinition
GrpcStatusFilter: MessageTypeDefinition
HeaderFilter: MessageTypeDefinition
NotHealthCheckFilter: MessageTypeDefinition
OrFilter: MessageTypeDefinition
ResponseFlagFilter: MessageTypeDefinition
RuntimeFilter: MessageTypeDefinition
StatusCodeFilter: MessageTypeDefinition
TraceableFilter: MessageTypeDefinition
}
}
network: {
http_connection_manager: {
v2: {
HttpConnectionManager: MessageTypeDefinition
HttpFilter: MessageTypeDefinition
Rds: MessageTypeDefinition
RequestIDExtension: MessageTypeDefinition
ScopedRds: MessageTypeDefinition
ScopedRouteConfigurationsList: MessageTypeDefinition
ScopedRoutes: MessageTypeDefinition
}
}
}
}
trace: {
v2: {
Tracing: MessageTypeDefinition
}
}
}
type: {
DoubleRange: MessageTypeDefinition
FractionalPercent: MessageTypeDefinition
Int32Range: MessageTypeDefinition
Int64Range: MessageTypeDefinition
Percent: MessageTypeDefinition
SemanticVersion: MessageTypeDefinition
matcher: {
ListStringMatcher: MessageTypeDefinition
RegexMatchAndSubstitute: MessageTypeDefinition
RegexMatcher: MessageTypeDefinition
StringMatcher: MessageTypeDefinition
}
metadata: {
v2: {
MetadataKey: MessageTypeDefinition
MetadataKind: MessageTypeDefinition
}
}
tracing: {
v2: {
CustomTag: MessageTypeDefinition
}
}
}
}
google: {
protobuf: {
Any: MessageTypeDefinition
BoolValue: MessageTypeDefinition
BytesValue: MessageTypeDefinition
DescriptorProto: MessageTypeDefinition
DoubleValue: MessageTypeDefinition
Duration: MessageTypeDefinition
Empty: MessageTypeDefinition
EnumDescriptorProto: MessageTypeDefinition
EnumOptions: MessageTypeDefinition
EnumValueDescriptorProto: MessageTypeDefinition
EnumValueOptions: MessageTypeDefinition
FieldDescriptorProto: MessageTypeDefinition
FieldOptions: MessageTypeDefinition
FileDescriptorProto: MessageTypeDefinition
FileDescriptorSet: MessageTypeDefinition
FileOptions: MessageTypeDefinition
FloatValue: MessageTypeDefinition
GeneratedCodeInfo: MessageTypeDefinition
Int32Value: MessageTypeDefinition
Int64Value: MessageTypeDefinition
ListValue: MessageTypeDefinition
MessageOptions: MessageTypeDefinition
MethodDescriptorProto: MessageTypeDefinition
MethodOptions: MessageTypeDefinition
NullValue: EnumTypeDefinition
OneofDescriptorProto: MessageTypeDefinition
OneofOptions: MessageTypeDefinition
ServiceDescriptorProto: MessageTypeDefinition
ServiceOptions: MessageTypeDefinition
SourceCodeInfo: MessageTypeDefinition
StringValue: MessageTypeDefinition
Struct: MessageTypeDefinition
Timestamp: MessageTypeDefinition
UInt32Value: MessageTypeDefinition
UInt64Value: MessageTypeDefinition
UninterpretedOption: MessageTypeDefinition
Value: MessageTypeDefinition
}
}
udpa: {
annotations: {
FieldMigrateAnnotation: MessageTypeDefinition
FileMigrateAnnotation: MessageTypeDefinition
MigrateAnnotation: MessageTypeDefinition
PackageVersionStatus: EnumTypeDefinition
StatusAnnotation: MessageTypeDefinition
}
}
validate: {
AnyRules: MessageTypeDefinition
BoolRules: MessageTypeDefinition
BytesRules: MessageTypeDefinition
DoubleRules: MessageTypeDefinition
DurationRules: MessageTypeDefinition
EnumRules: MessageTypeDefinition
FieldRules: MessageTypeDefinition
Fixed32Rules: MessageTypeDefinition
Fixed64Rules: MessageTypeDefinition
FloatRules: MessageTypeDefinition
Int32Rules: MessageTypeDefinition
Int64Rules: MessageTypeDefinition
KnownRegex: EnumTypeDefinition
MapRules: MessageTypeDefinition
MessageRules: MessageTypeDefinition
RepeatedRules: MessageTypeDefinition
SFixed32Rules: MessageTypeDefinition
SFixed64Rules: MessageTypeDefinition
SInt32Rules: MessageTypeDefinition
SInt64Rules: MessageTypeDefinition
StringRules: MessageTypeDefinition
TimestampRules: MessageTypeDefinition
UInt32Rules: MessageTypeDefinition
UInt64Rules: MessageTypeDefinition
}
}

View File

@ -31,6 +31,7 @@ import { LogVerbosity } from './constants';
import { SubchannelAddress, TcpSubchannelAddress } from './subchannel';
import { GrpcUri, uriToString, splitHostPort } from './uri-parser';
import { isIPv6, isIPv4 } from 'net';
import { ChannelOptions } from './channel-options';
const TRACER_NAME = 'dns_resolver';
@ -84,7 +85,11 @@ class DnsResolver implements Resolver {
private latestServiceConfigError: StatusObject | null = null;
private percentage: number;
private defaultResolutionError: StatusObject;
constructor(private target: GrpcUri, private listener: ResolverListener) {
constructor(
private target: GrpcUri,
private listener: ResolverListener,
channelOptions: ChannelOptions
) {
trace('Resolver constructed for target ' + uriToString(target));
const hostPort = splitHostPort(target.path);
if (hostPort === null) {

View File

@ -17,10 +17,15 @@
import { Resolver, ResolverListener, registerResolver } from './resolver';
import { SubchannelAddress } from './subchannel';
import { GrpcUri } from './uri-parser';
import { ChannelOptions } from './channel-options';
class UdsResolver implements Resolver {
private addresses: SubchannelAddress[] = [];
constructor(target: GrpcUri, private listener: ResolverListener) {
constructor(
target: GrpcUri,
private listener: ResolverListener,
channelOptions: ChannelOptions
) {
let path: string;
if (target.authority === '') {
path = '/' + target.path;

View File

@ -0,0 +1,82 @@
/*
* Copyright 2019 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.
*/
import { Resolver, ResolverListener, registerResolver } from './resolver';
import { GrpcUri, uriToString } from './uri-parser';
import { XdsClient } from './xds-client';
import { ServiceConfig } from './service-config';
import { StatusObject } from './call-stream';
import { Status } from './constants';
import { Metadata } from './metadata';
import { ChannelOptions } from './channel-options';
class XdsResolver implements Resolver {
private resolutionStarted = false;
private hasReportedSuccess = false;
constructor(
private target: GrpcUri,
private listener: ResolverListener,
private channelOptions: ChannelOptions
) {}
private reportResolutionError() {
this.listener.onError({
code: Status.UNAVAILABLE,
details: `xDS name resolution failed for target ${uriToString(
this.target
)}`,
metadata: new Metadata(),
});
}
updateResolution(): void {
// Wait until updateResolution is called once to start the xDS requests
if (!this.resolutionStarted) {
this.resolutionStarted = true;
const xdsClient = new XdsClient(
this.target.path,
{
onValidUpdate: (update: ServiceConfig) => {
this.hasReportedSuccess = true;
this.listener.onSuccessfulResolution([], update, null, {
xdsClient: xdsClient,
});
},
onTransientError: (error: StatusObject) => {
/* A transient error only needs to bubble up as a failure if we have
* not already provided a ServiceConfig for the upper layer to use */
if (!this.hasReportedSuccess) {
this.reportResolutionError();
}
},
onResourceDoesNotExist: () => {
this.reportResolutionError();
},
},
this.channelOptions
);
}
}
static getDefaultAuthority(target: GrpcUri) {
return target.path;
}
}
export function setup() {
registerResolver('xds', XdsResolver);
}

View File

@ -21,6 +21,7 @@ import * as resolver_uds from './resolver-uds';
import { StatusObject } from './call-stream';
import { SubchannelAddress } from './subchannel';
import { GrpcUri, uriToString } from './uri-parser';
import { ChannelOptions } from './channel-options';
/**
* A listener object passed to the resolver's constructor that provides name
@ -64,7 +65,11 @@ export interface Resolver {
}
export interface ResolverConstructor {
new (target: GrpcUri, listener: ResolverListener): Resolver;
new (
target: GrpcUri,
listener: ResolverListener,
channelOptions: ChannelOptions
): Resolver;
/**
* Get the default authority for a target. This loosely corresponds to that
* target's hostname. Throws an error if this resolver class cannot parse the
@ -108,10 +113,11 @@ export function registerDefaultScheme(scheme: string) {
*/
export function createResolver(
target: GrpcUri,
listener: ResolverListener
listener: ResolverListener,
options: ChannelOptions
): Resolver {
if (target.scheme !== undefined && target.scheme in registeredResolvers) {
return new registeredResolvers[target.scheme](target, listener);
return new registeredResolvers[target.scheme](target, listener, options);
} else {
throw new Error(
`No resolver could be created for target ${uriToString(target)}`

View File

@ -20,7 +20,7 @@ import {
LoadBalancer,
getFirstUsableConfig,
} from './load-balancer';
import { ServiceConfig } from './service-config';
import { ServiceConfig, validateServiceConfig } from './service-config';
import { ConnectivityState } from './channel';
import { createResolver, Resolver } from './resolver';
import { ServiceError } from './call';
@ -35,6 +35,7 @@ import { LogVerbosity } from './constants';
import { SubchannelAddress } from './subchannel';
import { GrpcUri, uriToString } from './uri-parser';
import { ChildLoadBalancerHandler } from './load-balancer-child-handler';
import { ChannelOptions } from './channel-options';
const TRACER_NAME = 'resolving_load_balancer';
@ -57,6 +58,7 @@ export class ResolvingLoadBalancer implements LoadBalancer {
* This resolving load balancer's current connectivity state.
*/
private currentState: ConnectivityState = ConnectivityState.IDLE;
private readonly defaultServiceConfig: ServiceConfig;
/**
* The service config object from the last successful resolution, if
* available. A value of null indicates that we have not yet received a valid
@ -90,8 +92,18 @@ export class ResolvingLoadBalancer implements LoadBalancer {
constructor(
private readonly target: GrpcUri,
private readonly channelControlHelper: ChannelControlHelper,
private readonly defaultServiceConfig: ServiceConfig | null
private readonly channelOptions: ChannelOptions
) {
if (channelOptions['grpc.service_config']) {
this.defaultServiceConfig = validateServiceConfig(
JSON.parse(channelOptions['grpc.service_config']!)
);
} else {
this.defaultServiceConfig = {
loadBalancingConfig: [],
methodConfig: [],
};
}
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
this.childLoadBalancer = new ChildLoadBalancerHandler({
createSubchannel: channelControlHelper.createSubchannel.bind(
@ -114,68 +126,72 @@ export class ResolvingLoadBalancer implements LoadBalancer {
this.updateState(newState, picker);
},
});
this.innerResolver = createResolver(target, {
onSuccessfulResolution: (
addressList: SubchannelAddress[],
serviceConfig: ServiceConfig | null,
serviceConfigError: ServiceError | null,
attributes: { [key: string]: unknown }
) => {
let workingServiceConfig: ServiceConfig | null = null;
/* This first group of conditionals implements the algorithm described
* in https://github.com/grpc/proposal/blob/master/A21-service-config-error-handling.md
* in the section called "Behavior on receiving a new gRPC Config".
*/
if (serviceConfig === null) {
// Step 4 and 5
if (serviceConfigError === null) {
// Step 5
this.previousServiceConfig = null;
workingServiceConfig = this.defaultServiceConfig;
} else {
// Step 4
if (this.previousServiceConfig === null) {
// Step 4.ii
this.handleResolutionFailure(serviceConfigError);
this.innerResolver = createResolver(
target,
{
onSuccessfulResolution: (
addressList: SubchannelAddress[],
serviceConfig: ServiceConfig | null,
serviceConfigError: ServiceError | null,
attributes: { [key: string]: unknown }
) => {
let workingServiceConfig: ServiceConfig | null = null;
/* This first group of conditionals implements the algorithm described
* in https://github.com/grpc/proposal/blob/master/A21-service-config-error-handling.md
* in the section called "Behavior on receiving a new gRPC Config".
*/
if (serviceConfig === null) {
// Step 4 and 5
if (serviceConfigError === null) {
// Step 5
this.previousServiceConfig = null;
workingServiceConfig = this.defaultServiceConfig;
} else {
// Step 4.i
workingServiceConfig = this.previousServiceConfig;
// Step 4
if (this.previousServiceConfig === null) {
// Step 4.ii
this.handleResolutionFailure(serviceConfigError);
} else {
// Step 4.i
workingServiceConfig = this.previousServiceConfig;
}
}
} else {
// Step 3
workingServiceConfig = serviceConfig;
this.previousServiceConfig = serviceConfig;
}
} else {
// Step 3
workingServiceConfig = serviceConfig;
this.previousServiceConfig = serviceConfig;
}
const workingConfigList =
workingServiceConfig?.loadBalancingConfig ?? [];
if (workingConfigList.length === 0) {
workingConfigList.push({
name: 'pick_first',
pick_first: {},
});
}
const loadBalancingConfig = getFirstUsableConfig(workingConfigList);
if (loadBalancingConfig === null) {
// There were load balancing configs but none are supported. This counts as a resolution failure
this.handleResolutionFailure({
code: Status.UNAVAILABLE,
details:
'All load balancer options in service config are not compatible',
metadata: new Metadata(),
});
return;
}
this.childLoadBalancer.updateAddressList(
addressList,
loadBalancingConfig,
attributes
);
const workingConfigList =
workingServiceConfig?.loadBalancingConfig ?? [];
if (workingConfigList.length === 0) {
workingConfigList.push({
name: 'pick_first',
pick_first: {},
});
}
const loadBalancingConfig = getFirstUsableConfig(workingConfigList);
if (loadBalancingConfig === null) {
// There were load balancing configs but none are supported. This counts as a resolution failure
this.handleResolutionFailure({
code: Status.UNAVAILABLE,
details:
'All load balancer options in service config are not compatible',
metadata: new Metadata(),
});
return;
}
this.childLoadBalancer.updateAddressList(
addressList,
loadBalancingConfig,
attributes
);
},
onError: (error: StatusObject) => {
this.handleResolutionFailure(error);
},
},
onError: (error: StatusObject) => {
this.handleResolutionFailure(error);
},
});
channelOptions
);
this.backoffTimeout = new BackoffTimeout(() => {
if (this.continueResolving) {

View File

@ -415,7 +415,7 @@ export class Server {
},
};
const resolver = createResolver(portUri, resolverListener);
const resolver = createResolver(portUri, resolverListener, this.options);
resolver.updateResolution();
}

View File

@ -47,6 +47,9 @@ import {
_envoy_api_v2_endpoint_ClusterStats_DroppedRequests,
} from './generated/envoy/api/v2/endpoint/ClusterStats';
import { UpstreamLocalityStats } from './generated/envoy/api/v2/endpoint/UpstreamLocalityStats';
import { Listener__Output } from './generated/envoy/api/v2/Listener';
import { HttpConnectionManager__Output } from './generated/envoy/config/filter/network/http_connection_manager/v2/HttpConnectionManager';
import { RouteConfiguration__Output } from './generated/envoy/api/v2/RouteConfiguration';
const TRACER_NAME = 'xds_client';
@ -58,6 +61,11 @@ const clientVersion = require('../../package.json').version;
const EDS_TYPE_URL = 'type.googleapis.com/envoy.api.v2.ClusterLoadAssignment';
const CDS_TYPE_URL = 'type.googleapis.com/envoy.api.v2.Cluster';
const LDS_TYPE_URL = 'type.googleapis.com/envoy.api.v2.Listener';
const RDS_TYPE_URL = 'type.googleapis.com/envoy.api.v2.RouteConfiguration';
const HTTP_CONNECTION_MANGER_TYPE_URL =
'type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager';
let loadedProtos: Promise<
adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType
@ -73,10 +81,12 @@ function loadAdsProtos(): Promise<
.load(
[
'envoy/service/discovery/v2/ads.proto',
'envoy/service/load_stats/v2/lrs.proto',
'envoy/api/v2/listener.proto',
'envoy/api/v2/route.proto',
'envoy/api/v2/cluster.proto',
'envoy/api/v2/endpoint.proto',
'envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto',
],
{
keepCase: true,
@ -238,6 +248,15 @@ export class XdsClient {
private lastCdsNonce = '';
private latestCdsResponses: Cluster__Output[] = [];
private lastLdsVersionInfo = '';
private lastLdsNonce = '';
private latestLdsResponse: Listener__Output | null = null;
private routeConfigName: string | null = null;
private lastRdsVersionInfo = '';
private lastRdsNonce = '';
private latestRdsResponse: RouteConfiguration__Output | null = null;
constructor(
private targetName: string,
private serviceConfigWatcher: Watcher<ServiceConfig>,
@ -308,6 +327,155 @@ export class XdsClient {
clearInterval(this.statsTimer);
}
private handleAdsResponse(message: DiscoveryResponse__Output) {
switch (message.type_url) {
case EDS_TYPE_URL: {
const edsResponses: ClusterLoadAssignment__Output[] = [];
for (const resource of message.resources) {
if (
protoLoader.isAnyExtension(resource) &&
resource['@type'] === EDS_TYPE_URL
) {
const resp = resource as protoLoader.AnyExtension &
ClusterLoadAssignment__Output;
if (!this.validateEdsResponse(resp)) {
this.nackEds('ClusterLoadAssignment validation failed');
return;
}
edsResponses.push(resp);
} else {
this.nackEds(
`Invalid resource type ${
protoLoader.isAnyExtension(resource)
? resource['@type']
: resource.type_url
}`
);
return;
}
}
for (const message of edsResponses) {
this.handleEdsResponse(message);
}
this.lastEdsVersionInfo = message.version_info;
this.lastEdsNonce = message.nonce;
this.latestEdsResponses = edsResponses;
this.ackEds();
break;
}
case CDS_TYPE_URL: {
const cdsResponses: Cluster__Output[] = [];
for (const resource of message.resources) {
if (
protoLoader.isAnyExtension(resource) &&
resource['@type'] === CDS_TYPE_URL
) {
const resp = resource as protoLoader.AnyExtension & Cluster__Output;
if (!this.validateCdsResponse(resp)) {
this.nackCds('Cluster validation failed');
return;
}
} else {
this.nackCds(
`Invalid resource type ${
protoLoader.isAnyExtension(resource)
? resource['@type']
: resource.type_url
}`
);
return;
}
}
for (const message of cdsResponses) {
this.handleCdsResponse(message);
}
this.lastCdsVersionInfo = message.version_info;
this.lastCdsNonce = message.nonce;
this.latestCdsResponses = cdsResponses;
this.ackCds();
break;
}
case LDS_TYPE_URL: {
let nackError: string | null = null;
for (const resource of message.resources) {
if (
protoLoader.isAnyExtension(resource) &&
resource['@type'] === LDS_TYPE_URL
) {
const resp = resource as protoLoader.AnyExtension &
Listener__Output;
if (resp.name === this.targetName) {
if (this.validateLdsResponse(resp)) {
this.handleLdsResponse(resp);
this.lastLdsVersionInfo = message.version_info;
this.lastLdsNonce = message.nonce;
this.latestLdsResponse = resp;
} else {
nackError = 'Listener validation failed';
}
break;
}
} else {
nackError = `Invalid resource type ${
protoLoader.isAnyExtension(resource)
? resource['@type']
: resource.type_url
}`;
break;
}
}
if (nackError) {
this.nackLds(nackError);
} else {
this.ackLds();
}
break;
}
case RDS_TYPE_URL: {
let nackError: string | null = null;
if (this.routeConfigName === null) {
nackError = 'Unexpected RouteConfiguration response';
} else {
for (const resource of message.resources) {
if (
protoLoader.isAnyExtension(resource) &&
resource['@type'] === RDS_TYPE_URL
) {
const resp = resource as protoLoader.AnyExtension &
RouteConfiguration__Output;
if (resp.name === this.routeConfigName) {
if (this.validateRdsResponse(resp)) {
this.handleRdsResponse(resp);
this.lastRdsVersionInfo = message.version_info;
this.lastRdsNonce = message.nonce;
this.latestRdsResponse = resp;
} else {
nackError = 'RouteConfiguration validation failed';
}
break;
}
} else {
nackError = `Invalid resource type ${
protoLoader.isAnyExtension(resource)
? resource['@type']
: resource.type_url
}`;
break;
}
}
}
if (nackError) {
this.nackRds(nackError);
} else {
this.ackRds();
}
break;
}
default:
this.nackUnknown(message.type_url, message.version_info, message.nonce);
}
}
/**
* Start the ADS stream if the client exists and there is not already an
* existing stream, and there
@ -324,81 +492,7 @@ export class XdsClient {
}
this.adsCall = this.adsClient.StreamAggregatedResources();
this.adsCall.on('data', (message: DiscoveryResponse__Output) => {
switch (message.type_url) {
case EDS_TYPE_URL: {
const edsResponses: ClusterLoadAssignment__Output[] = [];
for (const resource of message.resources) {
if (
protoLoader.isAnyExtension(resource) &&
resource['@type'] === EDS_TYPE_URL
) {
const resp = resource as protoLoader.AnyExtension &
ClusterLoadAssignment__Output;
if (!this.validateEdsResponse(resp)) {
this.nackEds('ClusterLoadAssignment validation failed');
return;
}
edsResponses.push(resp);
} else {
this.nackEds(
`Invalid resource type ${
protoLoader.isAnyExtension(resource)
? resource['@type']
: resource.type_url
}`
);
return;
}
}
for (const message of edsResponses) {
this.handleEdsResponse(message);
}
this.lastEdsVersionInfo = message.version_info;
this.lastEdsNonce = message.nonce;
this.latestEdsResponses = edsResponses;
this.ackEds();
break;
}
case CDS_TYPE_URL: {
const cdsResponses: Cluster__Output[] = [];
for (const resource of message.resources) {
if (
protoLoader.isAnyExtension(resource) &&
resource['@type'] === CDS_TYPE_URL
) {
const resp = resource as protoLoader.AnyExtension &
Cluster__Output;
if (!this.validateCdsResponse(resp)) {
this.nackCds('Cluster validation failed');
return;
}
} else {
this.nackEds(
`Invalid resource type ${
protoLoader.isAnyExtension(resource)
? resource['@type']
: resource.type_url
}`
);
return;
}
}
for (const message of cdsResponses) {
this.handleCdsResponse(message);
}
this.lastCdsVersionInfo = message.version_info;
this.lastCdsNonce = message.nonce;
this.latestCdsResponses = cdsResponses;
this.ackCds();
break;
}
default:
this.nackUnknown(
message.type_url,
message.version_info,
message.nonce
);
}
this.handleAdsResponse(message);
});
this.adsCall.on('error', (error: ServiceError) => {
trace(
@ -411,6 +505,30 @@ export class XdsClient {
* reconnect */
this.maybeStartAdsStream();
});
this.adsCall.write({
node: this.adsNode!,
type_url: LDS_TYPE_URL,
resource_names: [this.targetName],
});
if (this.routeConfigName) {
this.adsCall.write({
node: this.adsNode!,
type_url: RDS_TYPE_URL,
resource_names: [this.routeConfigName],
});
}
const clusterNames = Array.from(this.clusterWatchers.keys());
if (clusterNames.length > 0) {
this.adsCall.write({
node: this.adsNode!,
type_url: CDS_TYPE_URL,
resource_names: clusterNames,
});
}
const endpointWatcherNames = Array.from(this.endpointWatchers.keys());
if (endpointWatcherNames.length > 0) {
this.adsCall.write({
@ -466,6 +584,26 @@ export class XdsClient {
});
}
private ackLds() {
this.adsCall?.write({
node: this.adsNode!,
type_url: LDS_TYPE_URL,
resource_names: [this.targetName],
response_nonce: this.lastLdsNonce,
version_info: this.lastLdsVersionInfo,
});
}
private ackRds() {
this.adsCall?.write({
node: this.adsNode!,
type_url: RDS_TYPE_URL,
resource_names: [this.routeConfigName!],
response_nonce: this.lastRdsNonce,
version_info: this.lastRdsVersionInfo,
});
}
/**
* Reject an EDS update. This should be called without updating the local
* nonce and version info.
@ -502,6 +640,32 @@ export class XdsClient {
});
}
private nackLds(message: string) {
this.adsCall?.write({
node: this.adsNode!,
type_url: LDS_TYPE_URL,
resource_names: [this.targetName],
response_nonce: this.lastLdsNonce,
version_info: this.lastLdsVersionInfo,
error_detail: {
message,
},
});
}
private nackRds(message: string) {
this.adsCall?.write({
node: this.adsNode!,
type_url: RDS_TYPE_URL,
resource_names: this.routeConfigName ? [this.routeConfigName] : [],
response_nonce: this.lastRdsNonce,
version_info: this.lastRdsVersionInfo,
error_detail: {
message,
},
});
}
/**
* Validate the ClusterLoadAssignment object by these rules:
* https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md#clusterloadassignment-proto
@ -543,6 +707,36 @@ export class XdsClient {
return true;
}
private validateLdsResponse(message: Listener__Output): boolean {
if (
!(
message.api_listener?.api_listener &&
protoLoader.isAnyExtension(message.api_listener.api_listener) &&
message.api_listener?.api_listener['@type'] ===
HTTP_CONNECTION_MANGER_TYPE_URL
)
) {
return false;
}
const httpConnectionManager = message.api_listener
?.api_listener as protoLoader.AnyExtension &
HttpConnectionManager__Output;
switch (httpConnectionManager.route_specifier) {
case 'rds':
if (!httpConnectionManager.rds?.config_source?.ads) {
return false;
}
break;
case 'route_config':
return this.validateRdsResponse(httpConnectionManager.route_config!);
}
return false;
}
private validateRdsResponse(message: RouteConfiguration__Output): boolean {
return true;
}
private handleEdsResponse(message: ClusterLoadAssignment__Output) {
const watchers = this.endpointWatchers.get(message.cluster_name) ?? [];
for (const watcher of watchers) {
@ -557,6 +751,52 @@ export class XdsClient {
}
}
private handleLdsResponse(message: Listener__Output) {
// The validation step ensures that this is correct
const httpConnectionManager = message.api_listener!
.api_listener as protoLoader.AnyExtension & HttpConnectionManager__Output;
switch (httpConnectionManager.route_specifier) {
case 'rds':
this.routeConfigName = httpConnectionManager.rds!.route_config_name;
this.updateRdsNames();
break;
case 'route_config':
this.handleRdsResponse(httpConnectionManager.route_config!);
if (this.routeConfigName) {
this.routeConfigName = null;
this.updateRdsNames();
}
break;
default:
// The validation rules should prevent this
}
}
private handleRdsResponse(message: RouteConfiguration__Output) {
for (const virtualHost of message.virtual_hosts) {
if (virtualHost.domains.indexOf(this.routeConfigName!) >= 0) {
const route = virtualHost.routes[virtualHost.routes.length - 1];
if (route.match?.prefix === '' && route.route?.cluster) {
this.serviceConfigWatcher.onValidUpdate({
methodConfig: [],
loadBalancingConfig: [
{
name: 'cds',
cds: {
cluster: route.route.cluster,
},
},
],
});
break;
}
}
}
/* If none of the routes match the one we are looking for, bubble up an
* error. */
this.serviceConfigWatcher.onResourceDoesNotExist();
}
private updateEdsNames() {
if (this.adsCall) {
this.adsCall.write({
@ -581,16 +821,26 @@ export class XdsClient {
}
}
private updateRdsNames() {
this.adsCall?.write({
node: this.adsNode!,
type_url: RDS_TYPE_URL,
resource_names: this.routeConfigName ? [this.routeConfigName] : [],
response_nonce: this.lastRdsNonce,
version_info: this.lastRdsVersionInfo,
});
}
private reportStreamError(status: StatusObject) {
for (const watcherList of [
...this.endpointWatchers.values(),
...this.clusterWatchers.values(),
[this.serviceConfigWatcher],
]) {
for (const watcher of watcherList) {
watcher.onTransientError(status);
}
}
// Also do the same for other types of watchers when those are implemented
}
private maybeStartLrsStream() {

View File

@ -63,7 +63,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should default to port 443', done => {
@ -98,7 +98,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should correctly represent an ipv4 address', done => {
@ -125,7 +125,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should correctly represent an ipv6 address', done => {
@ -152,7 +152,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should correctly represent a bracketed ipv6 address', done => {
@ -179,7 +179,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should resolve a public address', done => {
@ -199,7 +199,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should resolve a name with multiple dots', done => {
@ -226,7 +226,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
/* TODO(murgatroid99): re-enable this test, once we can get the IPv6 result
@ -255,7 +255,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should resolve a DNS name to IPv4 and IPv6 addresses', done => {
@ -284,7 +284,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should resolve a name with a hyphen', done => {
@ -306,7 +306,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should resolve gRPC interop servers', done => {
@ -331,9 +331,9 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver1 = resolverManager.createResolver(target1, listener);
const resolver1 = resolverManager.createResolver(target1, listener, {});
resolver1.updateResolution();
const resolver2 = resolverManager.createResolver(target2, listener);
const resolver2 = resolverManager.createResolver(target2, listener, {});
resolver2.updateResolution();
});
});
@ -359,7 +359,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
it('Should handle an absolute Unix Domain Socket name', done => {
@ -384,7 +384,7 @@ describe('Name Resolver', () => {
done(new Error(`Failed with status ${error.details}`));
},
};
const resolver = resolverManager.createResolver(target, listener);
const resolver = resolverManager.createResolver(target, listener, {});
resolver.updateResolution();
});
});